我来自嵌入式C背景,我正在研究我的第一个C#应用程序而且我已经在这方面遇到了障碍,我的研究还没有完成,所以我想我会问这里。
简单的应用程序,到目前为止。我有一个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()。
this讨论的一个例外,它主张使用EventHandler并在主窗口中初始化它。这似乎不太理想,但也许我错了。
再往下,其他人主张数据共享。再一次,这对我来说似乎不太理想。
我读过的其他文章似乎已经过时了。
所以,我欢迎任何解释如何解决这个问题。可能是因为我只是在句法上被挂起但是我怀疑,尽管我认为我对代表,lambdas等大部分都很清楚,但我可能会对完全需要完成的事情感到困惑。
如果您可以通过一些解释说明在具体示例中如何完成,我将非常感激。
也许有些问题对我来说有点模糊:
1)我的工作人员可以独立访问它,还是必须通过UI的Dispatcher提供?
2)UI是否应该提供执行调度的委托,还是应该在worker任务中编写调度,引用UI Dispatcher?
非常感谢。
答案 0 :(得分:0)
关于提供样本的问题,如果有像......这样的工人阶级。
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线程上同步的责任不属于工作者。工作人员不应该关心,类似地,VM与线程无关,并通过其SynchronizationContext发布所有内容。
StatusText属性的代码如下所示......
private string _statusText;
public string StatusText
{
[DebuggerStepThrough]
get { return _statusText; }
[DebuggerStepThrough]
set
{
if (value != _statusText)
{
_statusText = value;
OnPropertyChanged("StatusText");
}
}
}
最后,在用户界面上,它就像这样呈现......
<StatusBar DockPanel.Dock="Bottom">
<TextBlock Text="{Binding StatusText}"/>
</StatusBar>
...
所以回顾一下你的问题:工作者线程可以访问它,但是他们应该不必须处理同步UI。这个责任就是虚拟机。 VM应与线程无关,并通过调度程序或同步上下文或其他方法同步UI。
如果您正在操作作为绑定主题的集合(例如,ObservableCollection),则通过Dispatcher进行调度是合适的。否则SynchronizationContext是合适的(它的重量更轻)。
答案 1 :(得分:-1)
只需添加委托并传递对主表单的引用
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!!")
}
}
}