C#/ WPF从另一个类中创建的另一个线程更新UI

我来自一个嵌入式C背景,我正在研究我的第一个C#应用程序,我已经碰壁了,我的研究还没有完成,所以我想我会问这里。

简单的app,到目前为止。 我有一个MainWindow,在一堆其他东西中,在按钮点击时启动一个TCPClient线程:

public partial class MainWindow : Window { .... TCPConnection myCon = new TCPConnection(); .... private void connectButton_Click(object sender, RoutedEventArgs e) { networkListBox.Items.Add("Connecting..."); myCon.Connect("localhost", updateNetworkListBox); } } .... public void updateNetworkListBox(string message) { networkListBox.Items.Add(message); } 

在TCPConnection.cs中:

  public class TCPConnection { .... public void Connect(string server, ReportDelegate reportDelegate) { this.server = server; clientThread = new Thread(() => Client(this.server)); clientThread.Start(); reportDelegate("Started client thread..."); } static void Client(string server) { try { Int32 port = 25565; TcpClient client = new TcpClient(server, port); Byte[] outgoingBytes = new Byte[1024]; string outgoingString = "Hello! I am " + Guid.NewGuid(); outgoingBytes = System.Text.Encoding.ASCII.GetBytes(outgoingString); NetworkStream stream = client.GetStream(); stream.Write(outgoingBytes, 0, outgoingBytes.Length); stream.Close(); client.Close(); } 

我想做的第一件事,现在TCP连接工作是将消息发送回UI,例如“客户端线程连接…”,“客户端线程连接…”并将其显示在networkListbox中。

在Connect()方法中,我能够通过使用委托来实现这一点,但这显然不适用于新线程,因为无法从另一个线程直接访问UI控件。

我已阅读了很多文章,我知道我可能想使用Dispatcher来做这件事。 但是,我见过的几乎所有示例都在当前类中创建了一个新线程,例如,将一个匿名方法传递给Dispatcher.Invoke()。

这个讨论的一个例外是主张使用EventHandler并在主窗口中初始化它。 这似乎不太理想,但也许我错了。

更进一步,其他人主张数据共享。 再说一遍,这对我来说似乎不太理想。

我读过的其他文章似乎已经过时了。

所以,我欢迎任何解释如何解决这个问题。 可能是因为我只是在语法上被挂起但我怀疑,虽然我认为我对代表,lambdas等大多是清楚的,但我可能会挂断确切需要完成的事情。

如果您可以通过一些解释说明如何在这个具体示例中完成,我将非常感激。

也许有些问题对我来说有点模糊:

1)我的工作人员可以独立访问它,还是必须提供UI的Dispatcher?

2)UI是否应该提供执行调度的委托,还是应该在worker任务中编写调度,引用UI Dispatcher?

非常感谢。

关于提供样本的问题,如果有类似的工人类……

 public class Worker { public Worker(Actionaction) { Task.Run(() => { int i = 0; while (true) { ++i; Task.Run(() => { action("Current value " + i); }); Task.Run(() => { // doing some work here }); Thread.Sleep(1000); } }); } } 

…在不同的线程上执行后台工作,并通过代表通知调用者。 委托是一个简单的vanilla,它接受一个字符串。 然后应该实现视图模型,使其不关心消息源自哪个线程。 这是VM中相应的代码……

  private readonly SynchronizationContext _context = SynchronizationContext.Current; private void StartWorker() { Worker w = new Worker((s) => _context.Post(delegate { StatusText = s; }, null)); } 

此代码使用SynchronizationContext,但可以轻松使用调度程序。 关键是在UI线程上同步的责任不属于工作者。 工作人员不应该关心,类似地,VM与线程无关,并通过其SynchronizationContext发布所有内容

StatusText属性的代码如下所示……

  private string _statusText; public string StatusText { [DebuggerStepThrough] get { return _statusText; } [DebuggerStepThrough] set { if (value != _statusText) { _statusText = value; OnPropertyChanged("StatusText"); } } } 

最后,在UI上,它呈现如下……

     

所以回顾一下你的问题:工作者线程可以访问它,但他们不应该处理同步UI。 这个责任就是虚拟机。 VM应与线程无关,并通过调度程序或同步上下文或其他方法同步UI。

如果您正在操作作为绑定主题的集合(例如,ObservableCollection),则通过Dispatcher进行调度是合适的。 否则SynchronizationContext是合适的(它的重量更轻)。

只需添加委托并传递对主表单的引用

 public partial class MainWindow : Window { TCPConnection myCon = new TCPConnection(); private void connectButton_Click(object sender, RoutedEventArgs e) { networkListBox.Items.Add("Connecting..."); myCon.Connect("localhost", updateNetworkListBox); } public delegate void updateNetworkListBoxDelegate(string message); public void updateNetworkListBox(string message) { if(this.invokeRequired()) { this.invoke(new updateNetworkListBoxDelegate(updateNetworkListBox), message); } else { networkListBox.Items.Add(message); } } } 

在TCPConnection中添加一个带有MainWindow实例的构造函数

 public class TCPConnection { //add member to hold instance private _mainWindow; //add constructor taking instance public TCPConnection(MainWindow mw) { _mainWindow = mw; } public void Connect(string server, ReportDelegate reportDelegate) { this.server = server; clientThread = new Thread(() => Client(this.server)); clientThread.Start(); //reportDelegate("Started client thread..."); //call the method on the UI thread _mainWindow.updateNetworkListBox("Started client thread..."); } static void Client(string server) { try { Int32 port = 25565; TcpClient client = new TcpClient(server, port); Byte[] outgoingBytes = new Byte[1024]; string outgoingString = "Hello! I am " + Guid.NewGuid(); outgoingBytes = System.Text.Encoding.ASCII.GetBytes(outgoingString); NetworkStream stream = client.GetStream(); stream.Write(outgoingBytes, 0, outgoingBytes.Length); stream.Close(); client.Close(); //call the method ont he ui thread _mainWindow.updateNetworkListBox("DONE!!") } } }