这是从后台线程通知事件主线程的正确方法吗?

时间:2019-03-21 13:48:43

标签: c# multithreading

background-thread是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));
    }

1 个答案:

答案 0 :(得分:1)

您在评论中写道:

  

给我的印象是,由于实例是在主线程上创建的,因此事件也将在此调用。

默认情况下,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(WinForms)或Control.BeginInvoke(WPF)代替Dispatcher.BeginInvoke

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