?
Solved

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

Posted on 2010-11-11
4
Medium Priority
?
344 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
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 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 2000 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

Free Tool: IP Lookup

Get more info about an IP address or domain name, such as organization, abuse contacts and geolocation.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

It was really hard time for me to get the understanding of Delegates in C#. I went through many websites and articles but I found them very clumsy. After going through those sites, I noted down the points in a easy way so here I am sharing that unde…
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 brief tutorial Pawel from AdRem Software explains how you can quickly find out which services are running on your network, or what are the IP addresses of servers responsible for each service. Software used is freeware NetCrunch Tools (https…
In this video, Percona Solution Engineer Rick Golba discuss how (and why) you implement high availability in a database environment. To discuss how Percona Consulting can help with your design and architecture needs for your database and infrastr…
Suggested Courses
Course of the Month12 days, 19 hours left to enroll

777 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