我来自嵌入式C背景,我正在开发我的第一个c#应用程序,我在这方面遇到了瓶颈,我的研究没有成功,所以我想我应该在这里问一下。
到目前为止还很简单。我有一个主窗口,在一堆其他的东西,启动一个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并在主窗口初始化它。这似乎不太理想,但也许我错了。
再往下,有人提倡数据共享。再一次,这对我来说似乎不太理想。
我看过的其他文章似乎都过时了。
所以,我欢迎任何关于如何做到这一点的解释。这可能是我只是在语法上被挂断了,但我怀疑,尽管我认为我对委托、lambda等都很清楚,但我可能对到底需要做什么感到挂断了。
如果你能在这个具体的例子中给出一些解释,我将非常感激。
可能还有一些我不太清楚的具体问题:
1)我的工作任务可以访问它自己还是必须与UI的Dispatcher提供?
2) UI应该提供一个执行调度的委托,还是应该将调度编码在工作任务中,引用UI Dispatcher?
对于您关于提供示例的问题,如果有一个worker类,如…
public class Worker
{
public Worker(Action<string>action)
{
Task.Run(() =>
{
int i = 0;
while (true)
{
++i;
Task.Run(() => { action("Current value " + i); });
Task.Run(() =>
{
// doing some work here
});
Thread.Sleep(1000);
}
});
}
}
…它在不同的线程上执行后台工作,并通过委托通知调用者。委托是一个普通的Action
,它接受一个字符串。然后,应该实现视图模型,使其不关心消息源自哪个线程。以下是VM中相应的代码…
private readonly SynchronizationContext _context = SynchronizationContext.Current;
private void StartWorker()
{
Worker w = new Worker((s) => _context.Post(delegate { StatusText = s; }, null));
}
这段代码使用了SynchronizationContext,但也可以简单地使用分派器。关键是UI线程上同步的责任不属于工作线程。worker不应该关心,类似地,VM是线程不可知的,并且通过它的SynchronizationContext发布所有内容。
StatusText属性的代码如下…
private string _statusText;
public string StatusText
{
[DebuggerStepThrough]
get { return _statusText; }
[DebuggerStepThrough]
set
{
if (value != _statusText)
{
_statusText = value;
OnPropertyChanged("StatusText");
}
}
}
最后,在UI上,它是这样呈现的…
<StatusBar DockPanel.Dock="Bottom">
<TextBlock Text="{Binding StatusText}"/>
</StatusBar>
…所以重述一下你的问题:工作线程可以访问它,但它们应该而不是必须处理同步UI。这是VM的责任。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!!")
}
}
}