简体   繁体   English

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

[英]Is this the proper way to inform the main thread of an event from a Background thread?

The background-thread is an UDP listener. 后台线程是UDP侦听器。 As soon as a certain type of message is received, the main thread has some work to do. 一旦接收到某种类型的消息,主线程便有一些工作要做。 My current solution works. 我当前的解决方案有效。 But I'm in doubt of the implementation. 但是我对实现方式表示怀疑。

My question: 我的问题:

  • Is this a proper way to handle these "simple" situations? 这是处理这些“简单”情况的正确方法吗?
  • Is there another simple and elegant solution that's more commonly accepted? 是否有另一种更普遍接受的简单优雅的解决方案?

The server class: 服务器类:

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();
        }
    }
}

Thread "safety" is sort of implemented in the way that the thread reading the message will only check the value when the Event has been triggered. 线程“安全”的实现方式是,读取消息的线程仅在触发事件后才检查该值。 The event will only be triggered when the value is completely written. 仅当值被完全写入时才触发该事件。 As long as each thread gets its own messenger instance, no trouble with threads sharing variables will arise, right? 只要每个线程都有自己的Messenger实例,共享变量的线程就不会出现问题,对吗?

The messenger class: 信使类:

//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);
    }
}

The main thread: 主线程:

    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));
    }

You write in the comments: 您在评论中写道:

I was under the impression that because the instance was created on the main thread, the event would be called there too. 我的印象是,由于实例是在主线程上创建的,因此该事件也会在主线程上调用。

Objects in C# and .NET are not thread-affine by default. 默认情况下,C#和.NET中的对象不是线程仿射的。 You need to implement this behavior manually. 您需要手动实现此行为。 Creating a generic object on a thread doesn't cause that object to raise events on the thread the object was created on. 在线程上创建通用对象不会导致该对象在创建对象的线程上引发事件。 Events are raised on the caller thread, if you don't provide a custom implementation that changes this. 如果您未提供可更改此设置的自定义实现,则会在调用者线程上引发事件。

With your current code, the StatusMessageEvent events will be raised on the caller thread (the one that runs the StartListening method). 使用当前代码,将在调用者线程(运行StartListening方法的线程)上引发StatusMessageEvent事件。

If you have a GUI application (eg WPF, WinForms), you can manually marshal to the main thread when necessary. 如果您有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() { }          
}

Instead of the SynchronizationContext , you can use Control.BeginInvoke (WinForms) or Dispatcher.BeginInvoke (WPF). 可以使用Control.BeginInvoke (WinForms)或Dispatcher.BeginInvoke (WPF)代替SynchronizationContext

If you have a console app and for some reason need to marshal to the main thread... Well, then you need to implement your own SynchronizationContext and something like a task scheduler or a dispatcher or a main loop. 如果您有控制台应用程序,并且由于某种原因需要编组到主线程...那么,那么您需要实现自己的SynchronizationContext以及诸如任务调度程序,调度程序或主循环之类的东西。 That's not easy. 那不容易。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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