Solved

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

Posted on 2010-11-11
4
327 Views
Last Modified: 2012-05-10
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

0
Comment
Question by:ImagineWhirledPeas
  • 3
4 Comments
 
LVL 33

Expert Comment

by:Todd Gerbert
ID: 34113064
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

0
 
LVL 33

Accepted Solution

by:
Todd Gerbert earned 500 total points
ID: 34113142
As a slightly unrelated note, you may choose to simply raise events in the On functions of ServerTerminal, to which WorkRepository subscribes and have WorkRepository's event handlers do the work, as opposed to doing the work in the On functions.
public class WorkRepository

{

  private ServerTerminal serverTerminal;

  private WorkRepository()

  {

    serverTerminal = new ServerTerminal();

    serverTerminal.MessageReceived += new EventHandler(serverTerminal_MessageReceived);

  }

  private void serverTerminal_MessageReceived(object sender, EventArgs e)

  {

    UpdateTask(...);

    UpdateWork(...);

  }

  private Guid UpdateWork(...) { }

  private void UpdateTask(...) { }



}



public class ServerTerminal

{

  public event EventHandler MessageReceived;

  private void Listen()

  {

    // Message received, raise the event

    OnMessageReceived(new EventArgs());

  }

  protected virtual void OnMessageReceived(EventArgs e)

  {

    if (MessageReceived != null)

      MessageReceived(this, e);

  }

}

Open in new window

0
 

Author Closing Comment

by:ImagineWhirledPeas
ID: 34113428
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
0
 
LVL 33

Expert Comment

by:Todd Gerbert
ID: 34113472
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.
0

Featured Post

Do You Know the 4 Main Threat Actor Types?

Do you know the main threat actor types? Most attackers fall into one of four categories, each with their own favored tactics, techniques, and procedures.

Join & Write a Comment

Calculating holidays and working days is a function that is often needed yet it is not one found within the Framework. This article presents one approach to building a working-day calculator for use in .NET.
Since pre-biblical times, humans have sought ways to keep secrets, and share the secrets selectively.  This article explores the ways PHP can be used to hide and encrypt information.
In this seventh video of the Xpdf series, we discuss and demonstrate the PDFfonts utility, which lists all the fonts used in a PDF file. It does this via a command line interface, making it suitable for use in programs, scripts, batch files — any pl…
In this tutorial you'll learn about bandwidth monitoring with flows and packet sniffing with our network monitoring solution PRTG Network Monitor (https://www.paessler.com/prtg). If you're interested in additional methods for monitoring bandwidt…

746 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

11 Experts available now in Live!

Get 1:1 Help Now