Solved

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

Posted on 2010-11-11
4
340 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 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

[Webinar] How Hackers Steal Your Credentials

Do You Know How Hackers Steal Your Credentials? Join us and Skyport Systems to learn how hackers steal your credentials and why Active Directory must be secure to stop them. Thursday, July 13, 2017 10:00 A.M. PDT

Question has a verified solution.

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

Introduction HyperText Transfer Protocol (http://www.ietf.org/rfc/rfc2616.txt) or "HTTP" is the underpinning of internet communication.  As a teacher of web development I have heard many questions, mostly from my younger students who have come to t…
A Change in PHP Behavior with Session Write Short Circuit (http://php.net/manual/en/book.session.php#116217) (Winter 2014)** With the release of PHP 5.6 the session handler changed in a way that many think should be considered a bug.  See the note …
Monitoring a network: why having a policy is the best policy? Michael Kulchisky, MCSE, MCSA, MCP, VTSP, VSP, CCSP outlines the enormous benefits of having a policy-based approach when monitoring medium and large networks. Software utilized in this v…
If you’ve ever visited a web page and noticed a cool font that you really liked the look of, but couldn’t figure out which font it was so that you could use it for your own work, then this video is for you! In this Micro Tutorial, you'll learn yo…

688 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