简体   繁体   中英

raising cross thread events in a DLL without a GUI in C#

I've written a DLL that does a bunch of stuff. One of the things it does is to search through a lot of incoming messages for a specific message, clean up the message, and raise an event when the message is found, and pass the data via the EventArgs.

Currently the DLL is working but the routine that searches for the message is slowing everything down and making the system slow to respond because there is so much data to go through.

I would like to move this searching to it's own thread, and once the message is found have it raise an event on the main thread and pass the message data to the main thread. I know how to make a thread to do the work, but I do not know how to make the thread raise the event on the main thread and pass the data to it. Could someone tell me how to do this, or point to an example of it?

I've tried creating a delegate and triggering an event, which works but when I try to pass the data I get a cross thread exception.

Some details that may be important. There most likely won't be a GUI at all and will probably remain a console application. Messages will always be coming in and the DLL has to continuously search them. The message the DLL is looking for may come as often as 1 couple times a second or it may be days between the messages.

Update for a real simple project illustrating what I would like to do. This code is rough cause I threw it together to test this out.

If you follow the code you will see that everything runs fine until the line in the CollectData function where:

StatusUpdated(this, null);

is called. At this point it causes the cross thread exception because of the UI thread and the data Collection thread. I know I can fix this by using an invoke on the UI thread, but as mentioned above this will most likely not be used with any sort of GUI (but it should be able to handle it). So the cross thread exception needs to be fixed in the BuildResultsManager class, I think in the function BRMgr_StatusUpdated. How do I get the data (in this case the value of currentVal) to the UI thread without changing any code in the MainWindow class.

You can run this by creating a solution with 2 projects, 1 for the first 2 files as a dll. The second as a wpf project, referencing the dll, and put a textbox named status on the form.

Main Class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace BuildResultsManager
{
/// <summary>
/// Delegate to notify when data has changed
/// </summary>
/// <param name="sender">unused</param>
/// <param name="e">unused</param>
public delegate void StatusUpdatedEventHandler(object sender, EventArgs e);

/// <summary>
/// Connects to the PLC via PVI and reads in all the build results data, conditions it, updates values and reads/store into the database
/// </summary>
public class BuildResultsManager
{
    #region events
    // Change in The Build Results Manger Status
    public event StatusUpdatedEventHandler StatusUpdated;
    #endregion

    #region Local variables
    private Thread collectionThread;
    private string statusMessage;
    DataCollector dataCollection;
    #endregion

    #region Properties
    /// <summary>
    /// Current Status of the Build Results manager
    /// </summary>
    public String StatusMessage
    {
        get
        {
            return statusMessage;
        }
        private set
        {
            statusMessage = value;
            if (StatusUpdated != null)
                StatusUpdated(this, null);
        }
    }
    #endregion

    #region Constructors
    /// <summary>
    /// Default constructor
    /// </summary>
    public BuildResultsManager()
    {
        StatusMessage = "successfully initialized";

        // start the thread that will collect all the data
        dataCollection = new DataCollector();
        dataCollection.StatusUpdated += new DCStatusUpdatedEventHandler(BRMgr_StatusUpdated);
        collectionThread = new Thread(new ThreadStart(dataCollection.CollectData));
        collectionThread.Start();
    }

    /// <summary>
    /// EVent to handle updated status text
    /// </summary>
    /// <param name="sender">unused</param>
    /// <param name="e">unused</param>
    void BRMgr_StatusUpdated(object sender, EventArgs e)
    {
        StatusMessage = dataCollection.currentVal.ToString();
    }

    #endregion

}
}

The class that will be doing all of the thread work:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Windows.Threading;
using System.Threading;

namespace BuildResultsManager
{
/// <summary>
/// Delegate to notify when data has changed
/// </summary>
/// <param name="sender">unused</param>
/// <param name="e">unused</param>
public delegate void DCStatusUpdatedEventHandler(object sender, EventArgs e);

/// <summary>
/// Handles all of the data collection and conditioning
/// </summary>
class DataCollector
{
    #region events
    // Change in The Build Results Manger Status
    public event DCStatusUpdatedEventHandler StatusUpdated;
    #endregion

    private const long updateInterval = 1000;
    private Stopwatch updateTimer;       
    public int currentVal;

    #region local variables
    private bool shouldStop;
    #endregion

    /// <summary>
    /// Default Constructor
    /// </summary>
    public DataCollector()
    {
        shouldStop = false;

        updateTimer = new Stopwatch();
        updateTimer.Start();            
    }

    /// <summary>
    /// Main task that listens for new data and collects it when it's available
    /// </summary>
    public void CollectData()
    {
        currentVal = 5;

        while (!shouldStop && currentVal < 10)
        {
            if(updateTimer.ElapsedMilliseconds > updateInterval)
            {
                currentVal++;
                if (StatusUpdated != null)
                {
                    StatusUpdated(this, null);
                }

                //reset the timer
                updateTimer.Restart();
            }
        }
    }

    /// <summary>
    /// Asks the thread to stop
    /// </summary>
    public void RequestStop()
    {
        shouldStop = true;
    }
}
}

Code behind for the wpf project:

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    #region Local variables
    private string BRMgrStatus;
    private BuildResultsManager.BuildResultsManager BRMgr;
    #endregion

    public MainWindow()
    {
        InitializeComponent();

        // create an instance of the build manager
        BRMgr = new BuildResultsManager.BuildResultsManager();
        if(BRMgr != null)
        {
            status.Text = BRMgr.StatusMessage;
            BRMgr.StatusUpdated += new StatusUpdatedEventHandler(BRMgr_StatusUpdated);
        }
    }

    /// <summary>
    /// EVent to handle updated status text
    /// </summary>
    /// <param name="sender">unused</param>
    /// <param name="e">unused</param>
    void BRMgr_StatusUpdated(object sender, EventArgs e)
    {
        BuildResultsManager.BuildResultsManager brm;
        brm = (BuildResultsManager.BuildResultsManager)sender;
        status.Text = brm.StatusMessage;
    }
}

如果您不使用GUI,则无需在同一线程上执行操作,只需使用锁定机制(锁,互斥锁等)保护非线程安全资源,以避免并发访问,那么您就很好。

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM