首页 > 解决方案 > 这是从后台线程通知主线程事件的正确方法吗?

问题描述

后台线程是一个 UDP 侦听器。一旦收到某种类型的消息,主线程就有一些工作要做。我目前的解决方案有效。但我对实施表示怀疑。

我的问题:

服务器类:

class UdpServer
{
    UdpClient listener;

    Messenger messenger;

    public UdpServer(Messenger messenger)
    {
        this.messenger= messenger;
    }

    public void StartListening()
    {
        listener = new UdpClient(settings.Port);
        IPEndPoint groupEP = new IPEndPoint(IPAddress.Any, 15000);

        try
        {
            while(true)
            {
                byte[] bytes = listener.Receive(ref groupEP);

                messenger.Message = string.Format("{0} : {1}", 
                    groupEP.ToString(), 
                    Encoding.ASCII.GetString(bytes, 0, bytes.Length));
            }
        }
        catch(SocketException e)
        {
            messenger.Message = string.Format("UDP server error: {0}", e.Message);
        }
        finally
        {
            listener.Close();
        }
    }
}

线程“安全”的实现方式是,读取消息的线程仅在触发事件时才检查值。只有当值被完全写入时才会触发该事件。只要每个线程都有自己的messenger实例,线程共享变量就不会出现问题,对吧?

信使类:

//this class is used to transport messages from the receiving threads to the main UI thread.
//subscribe to statusmessageevent in order to receive the messages

class Messenger
{
    private string message;       
    public string Message
    {
        set
        {
            message = value;
            StatusMessageEventHandler(message);
        }
    }

    public event EventHandler<string> StatusMessageEvent;
    private void StatusMessageEventHandler(string message)
    {
        StatusMessageEvent?.Invoke(this, message);
    }
}

主线:

    static void Main(string[] args)
    {

        var UdpMessenger = new Messenger();

        UdpMessenger.StatusMessageEvent += MessengerEvent;

        var UdpServer = new UdpServer(UdpMessenger);

        Task.Factory.StartNew(() => UdpServer.StartListening());

        Console.ReadKey();
    }


    private static void MessengerEvent(object sender, string e)
    {
        Console.WriteLine(string.Format("Received Message: {0}", e));
    }

标签: c#multithreading

解决方案


你在评论中写道:

我的印象是,因为实例是在主线程上创建的,所以也会在那里调用事件。

默认情况下,C# 和 .NET 中的对象不是线程仿射的。您需要手动实现此行为。在线程上创建通用对象不会导致该对象在创建对象的线程上引发事件。如果您不提供更改此事件的自定义实现,则会在调用者线程上引发事件。

使用您当前的代码,StatusMessageEvent事件将在调用者线程(运行该StartListening方法的线程)上引发。

如果您有一个 GUI 应用程序(例如 WPF、WinForms),您可以在必要时手动编组到主线程。

class Program
{
    static SynchronizationContext mainThreadContext;

    static void Main()
    {
        // app initialization code here

        // you could also do this somewhere in your main window
        mainThreadContext = SynchronizationContext.Current;
    }

    static void MessengerEvent(object sender, EventArgs<string> e)
    {
        // do additional stuff here that can be done on a background thread

        // The UpdateUI method will be executed on the UI thread
        mainThreadContext.Post(_ => UpdateUI(), null);
    }

    static void UpdateUI() { }          
}

SynchronizationContext您可以使用Control.BeginInvoke(WinForms) 或Dispatcher.BeginInvoke(WPF)代替.

如果你有一个控制台应用程序并且由于某种原因需要编组到主线程......那么你需要实现你自己的SynchronizationContext以及类似任务调度程序或调度程序或主循环的东西。这并不容易。


推荐阅读