c# - 这是从后台线程通知主线程事件的正确方法吗?
问题描述
后台线程是一个 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# 和 .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
以及类似任务调度程序或调度程序或主循环的东西。这并不容易。
推荐阅读
- javascript - 如何在不打开新浏览器窗口的情况下使用链接打开外部应用程序?
- ruby-on-rails - 如何设置 Sidekiq 来处理一个类的 Webhook
- python - 相互比较字典列表的所有 url
- c# - 角度应用程序正在获得访问权限,但从另一个系统调用时未访问 webapi,但在服务器内尝试工作。为什么?
- python - Python,为什么没有定义capsys?
- reactjs - 如何存储二维数组的先前状态?
- python - 如何将此时间戳转换为 Oracle Developer 的另一个输出?
- networking - 网络分段允许横向扩展应用程序?
- javascript - 在 JavaScript 中,Math 对象 - 为什么它不处理数字中的逗号?
- laravel - 如何使用 Laravel 在 Shopify API 中更新多个产品