Link to home
Start Free TrialLog in
Avatar of ImagineWhirledPeas
ImagineWhirledPeas

asked on

Will a separate listening thread make a WinForm miss a change event?

Hello,

I am seeing some strange behavior in my application and I'm wondering if I spawn a separate listening thread to update my data source, is my form missing the change event because they are on seprate threads?  Sometimes the form displays the data update, sometimes it does not and there is no exception to examine.  I am not so familiar with multitheaded applications, but my code is a combination of code examples I've found for the repository and the socket, so I'm not sure I haven't mixed ideas in a way that doesn't put the update and the change event on separate threads.  Basically, my display form has a singleton DataSet as its datasource and my ServerTerminal class spawns a new thread to listen for incoming messages.  Handlers are called that populate the DataSet.  Again, I'm wondering if the change event that drives the form content is fired on a different thread than the repository update is taking place on.  I'd appreciate any help from a multithreaded app veteran.  

Here is the how the Dataset is defined:

using System;
using System.Data;
using System.Collections.Generic;
using GanttChart.DataAccess;

namespace GanttChart.Model
{
   public class WorkRepository
   {
      private ServerTerminal serverTerminal;
      private DataSet WorkRepo;
      private DataTable theVendors;
      private DataTable theTasks;

      private static readonly WorkRepository instance;
      public static WorkRepository Instance
      {
         get { return instance; }
      }
      static WorkRepository()
      {
         instance = new WorkRepository();
      }

      private WorkRepository()
      {
         InitializeRepos();
         serverTerminal = new ServerTerminal();
         serverTerminal.StartListen(2535);
      }
      public void InitializeRepos()
      {
         WorkRepo = new DataSet();

         theVendors = WorkRepo.Tables.Add("Works");
         theVendors.Columns.Add("VendorGuid");
         theVendors.Columns.Add("VendorID");
         theVendors.Columns.Add("VendorName");
         theVendors.Columns.Add("WorkStartTime", typeof(DateTime));
         
         DataColumn[] keys = new DataColumn[1];
         keys[0] = theVendors.Columns["VendorID"];
         theVendors.PrimaryKey = keys;

         theTasks = WorkRepo.Tables.Add("Tasks");
         theTasks.Columns.Add("TaskGuid");
         theTasks.Columns.Add("VendorID");
         theTasks.Columns.Add("TaskName");
         theTasks.Columns.Add("TaskStartTime", typeof(DateTime));
         theTasks.Columns.Add("TaskDuration", typeof(TimeSpan));
         theTasks.Columns.Add("ParentTaskGuid");
         theTasks.Columns.Add("TaskPercentComplete");
         theTasks.Columns.Add("AllProperties", typeof(Byte[]));

         DataColumn[] tasksKey = new DataColumn[1];
         tasksKey[0] = theTasks.Columns["TaskGuid"];
         theTasks.PrimaryKey = tasksKey;

         System.Data.DataRelation relOrdDet;
         System.Data.DataColumn colMaster;
         System.Data.DataColumn colDetail;
         colMaster = WorkRepo.Tables["Works"].Columns["VendorID"];
         colDetail = WorkRepo.Tables["Tasks"].Columns["VendorID"];
         relOrdDet = new System.Data.DataRelation("RelOrdDet", colMaster, colDetail);
         WorkRepo.Relations.Add(relOrdDet);
      }

      public Guid UpdateWork(WorkData Work)
      {
         Guid id;
         DataRow row = theVendors.Rows.Find(Work.vehId);
         if (row == null)
         {
            // need to insert row
            id = new Guid();
            theVendors.Rows.Add(new Object[] { id, Work.vehId, Work.name, Work.startTime });
         }
         else
         {
            // need to update row
            string idstring = row["VendorGuid"].ToString();
            id = new Guid(idstring);
         }
         return id;
      }

      public void UpdateTask(TaskData task)
      {
         DataRow row = theTasks.Rows.Find(task.parentGuid);
         if (row == null)
         {
            // need to insert row
            Guid id = new Guid(task.parentGuid);
            theTasks.Rows.Add(new Object[] { id, task.VendorID, task.VendorName, DateTime.Now, TimeSpan.FromMinutes(20), null, null });
         }
         else
         {
            // need to update row
         }

         // Add task as a child of the Vendor summary task
         theTasks.Rows.Add(new Object[] { Guid.NewGuid(), task.VendorID, task.name, DateTime.Now, task.duration, new Guid(task.parentGuid), task.taskPercentageComplete });
      }

      public DataSet GetWorkRepository()
      {
         return WorkRepo;
      }

      public string GetVendorCompanyName(string key)
      {
         string CompanyName;
         DataRow row = theVendors.Rows.Find(key);
         if (row != null)
         {
            CompanyName = row.Field<string>("VendorName");
            return CompanyName;
         }
         else
         {
            CompanyName = null;
         }
         return CompanyName;
      }
   }

  public class WorkData
   {
      public Guid VendorGuid;
      public string name;
      public DateTime startTime;
      public string vehId;
   }

   public class TaskData
   {
      public string taskGuid;
      public string VendorID;
      public string name;
      public string VendorName;
      public DateTime taskStart;
      public TimeSpan duration;
      public string parentGuid;
      public string taskPercentageComplete;
   }
}

Open in new window


and here is how the socket code and handlers are defined:  
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading; 
using System.Windows.Forms;
using System.Runtime.Remoting.Messaging;
using System.Collections.Generic;
using GanttChart.Model;

namespace GanttChart.DataAccess
{
   public class ServerTerminal
   {
    private delegate void ProcessMsgDelegate(byte[] dgram);

    private Thread _listenThread;
    private UdpClient _client;

    
    private CoreMsgProcessor _processor = new CoreMsgProcessor("Listener", true);

    public void StartListen(int listenPort)
    {
      _processor.VendorIdMsgReceived += OnVendorIdentificationMsgReceived;
      _processor.PayloadDeliveryLocationMsgReceived += OnPayloadDeliveryLocationMsgReceived;
      _processor.InvalidMsgReceived += OnInvalidMsgReceived;
      _client = new UdpClient(listenPort);
      _listenThread = new Thread(new ThreadStart(Listen));
      _listenThread.Name = "Listen Thread";
      _listenThread.Start();
    }

    private void Listen()
    { 
      IPEndPoint ep = new IPEndPoint(IPAddress.Any, 0);
      
      ProcessMsgDelegate del = new ProcessMsgDelegate(_processor.ProcessMsg);
      AsyncCallback callback = new AsyncCallback(ProcessMsgCallback);

      for (; ; )
      {
        byte[] dgram = _client.Receive(ref ep);

        // This info can be useful if you care who sent the datagram.
        System.Diagnostics.Debug.WriteLine("Message Received");
        string a = ep.Port.ToString();
        System.Diagnostics.Debug.WriteLine(ep.Address.ToString() + ":" + ep.Port.ToString() + "\n");

        IAsyncResult ar = del.BeginInvoke(dgram, callback, null);
      }
    }

    private void ProcessMsgCallback(IAsyncResult ar)
    {
       AsyncResult result = (AsyncResult)ar;
       ProcessMsgDelegate del = (ProcessMsgDelegate)(result.AsyncDelegate);
       del.EndInvoke(ar);
    }


    private void OnInvalidMsgReceived(object sender, InvalidMsgEventArgs e)
    {
        System.Diagnostics.Debug.WriteLine("Invalid received. Reason: " + e.Reason + ".\r\n");
    }

    private void OnVendorIdentificationMsgReceived(object sender, VendorIdMsgReceivedEventArgs e)
    {
       WorkData Data = new WorkData();
       Data.VendorGuid = new Guid();
       Data.vehId = e.Message.VendorId.ToString();
       Data.name = e.Message.AtcCompanyName;
       WorkRepository.Instance.UpdateWork(Data);
    }

    private void OnPayloadDeliveryLocationMsgReceived(object sender, PayloadDeliveryLocationMsgReceivedEventArgs e)
    {
        WorkData Data = new WorkData();
        Data.vehId = e.Message.VendorId.ToString();
        Data.name = LookupVendorName(e.Message.VendorId);
        if (Data.name == null)
           Data.name = "VendorID = " + e.Message.VendorId;
        Data.startTime = ConvertDateTime(e.Message.TimeStamp);
        Guid id = WorkRepository.Instance.UpdateWork(Data);

        double lat = e.Message.GPSLatitude * 57.295779513082320876798154814105;
        double lon = e.Message.GPSLongitude * 57.295779513082320876798154814105;

        TaskData Task = new TaskData();
        Task.taskGuid = e.Message.MsgInstanceId.ToString();
        Task.VendorID = e.Message.VendorId.ToString();
        Task.taskStart = Data.startTime;
        TimeSpan a = new TimeSpan(Data.startTime.Hour, Data.startTime.Minute, Data.startTime.Second);
        Task.name = "Stare at (Lat,Lon):  " + lat + " , " + lon;
        TimeSpan span = new TimeSpan(-1, -1, -1);
        Task.duration = span.Duration();
        Task.taskPercentageComplete = Convert.ToString(25);
        Task.parentGuid = id.ToString();
        Task.VendorName = Data.name;
  WorkRepository.Instance.UpdateTask(Task);                        System.Diagnostics.Debug.WriteLine("OnPayloadDeliveryLocationMsgReceived");
    }
   }
}

Open in new window


Given this setup, is it possible that my form's change event is not firing?  If so, what can I do to fix it?  

Thanks in advance!
IWP

Avatar of Todd Gerbert
Todd Gerbert
Flag of United States of America image

I don't see where OnPayloadDeliveryLocationMsgReceived and OnVendorIdentificationMsgReceived are being called, I assume from _processor.ProcessMsg (which is called via a delegate on line 49 in your second code block).
Since Delegate.BeginInvoke uses thread pool threads, that means your On functions are also running in that seperate thread, which means you could potentially have concurrent calls to WorkRepository.UpdateTask and WorkRepository.UpdateWork making conflicting updates.
At the least you should implement some thread synchronization, using the "lock" keyword would probably be a good place to start.

public class WorkRepository
{
    private object updateWorkLock = new Object();

    ...

    public Guid UpdateWork()
    {
        // While this code-block is running
        // other threads also attempting to 
        // lock on updateWorkLock will be
        // blocked until this block is finished
        lock (updateWorkLock)
        {
            ...
        }
    }
}

Open in new window

ASKER CERTIFIED SOLUTION
Avatar of Todd Gerbert
Todd Gerbert
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of ImagineWhirledPeas
ImagineWhirledPeas

ASKER

Thank you tgerbert!  I like the idea of making the Data Access subscribe to the Model -- that seems to fit in with the MVVM design pattern I was going for and it makes keeps the update on the main thread.  Thanks again for your help.  

IWP
Sorry, I probably should have pointed out changing the event subscription model won't have any impact on which thread the event is raised.
The event handler serverTerminal_MessageReceived in WorkRepository will run on the same thread that invoked OnMessageReceived in the ServerTerminal class, which in this case is the seperate thread which is used by del.BeginInvoke in the listener thread.  You will still need a thread synchronization mechanism.