[英]Raising events during Tasks causing Cross-Thread Exception
I wrote a Client/Server async classes which works fine in the console. 我编写了一个Client / Server异步类,该类在控制台中可以正常工作。 I created a WinForm project for the server which subscribes to an event thrown by the server when there is a
.Pending()
connection and writes some messages into a textbox - which causes a Cross-Thread exception. 我为服务器创建了一个WinForm项目,当存在
.Pending()
连接时,该项目订阅了服务器引发的事件,并将一些消息写入文本框-导致跨线程异常。 The exception does not surprise me, however I am looking for a way to invoke that event without causing this exception, without handling it on the GUI/Control with .InvokeRequired
and .Invoke
- if that is even possible? 异常不会令我惊讶,但是我正在寻找一种方法来调用该事件而不会导致此异常,而无需使用
.InvokeRequired
和.Invoke
在GUI / Control上.InvokeRequired
进行.Invoke
-如果可能的话?
The server is started like that: 这样启动服务器:
Server server = new Server(PORT);
server.RunAsync();
in .RunAsync()
i just iterate over the network devices and set them to listening and invoke an event that the server has started, this also writes into the GUI however without any issue. 在
.RunAsync()
我仅遍历网络设备并将其设置为侦听并调用服务器已启动的事件,这也将写入GUI,但是没有任何问题。
public async Task RunAsync()
{
GetNetworkDevicesReady(Port);
await Task.Factory.StartNew(() =>
{
Parallel.ForEach(networkListeners, (listener) =>
{
Write.Info($"LISTENING ON {listener.LocalEndpoint}");
listener.Start();
});
});
IsRunning = true;
OnServerStarted?.Invoke(this, networkListeners.Where(l=>l.Active).ToList());
}
The code below is registered on the Form.Load
event and does not cause a Cross-Thread exception when writing "SERVER STARTED" in the textbox. 下面的代码在
Form.Load
事件中注册,并且在文本框中写入“ SERVER STARTED”时不会导致跨线程异常。
server.OnServerStarted += (s, a) =>
{
consoleWindow1.Event("SERVER STARTED", $"{Environment.NewLine}\t{string.Join($"{Environment.NewLine}\t", a.Select(x=>x.LocalEndpoint))}");
consoleWindow1.Event("WAITING FOR PENDING CONNECTIONS");
server.WaitForConnectionsAsync();
};
And this is the code which runs indefinite until a cancellation token is triggered: 这是无限期运行的代码,直到触发取消令牌:
public async Task WaitForConnectionsAsync()
{
waitingForConnectionsToken = new CancellationTokenSource();
await (waitinfConnectionTaks=Task.Factory.StartNew(async () =>
{
while (!waitingForConnectionsToken.IsCancellationRequested)
{
foreach (var listener in networkListeners)
{
if (waitingForConnectionsToken.IsCancellationRequested) break;
if (!listener.Active)
{
continue;
}
if (listener.Pending())
{
try
{
TcpClient connection = await listener.AcceptTcpClientAsync();
//TODO: need to send it synchronised, since this causes a Cross-Thread when using WinForms
OnPendingConnection?.Invoke(this, connection);
}
catch (ObjectDisposedException x)
{
Write.Error(x.ToString());
}
}
}
}
}));
}
I know I can use the textbox .InvokeRequired
and .Invoke
on the GUI but I have the feeling that the server should throw the event in a way the GUI doesn't cause a Cross-Thread exception. 我知道我可以在GUI上使用文本框
.InvokeRequired
和.Invoke
,但是我感觉服务器应该以GUI不会导致跨线程异常的方式抛出事件。
Is there a way to invoke the eventhandler in that "infinite task" without causing this exception? 有没有一种方法可以在该“无限任务”中调用事件处理程序而不会导致此异常?
Thanks to the comments and a good portion of sleep I solved my issue by changing the WaitForConnectionsAsync
to the following code: 多亏了注释和充足的睡眠,我通过将
WaitForConnectionsAsync
更改为以下代码解决了我的问题:
List<TcpClient> connections = new List<TcpClient>();
public async Task WaitForConnectionsAsync()
{
await (waitinfConnectionTaks = Task.Factory.StartNew(async () =>
{
//REMOVED LOOP
// while (!waitingForConnectionsToken.IsCancellationRequested)
{
foreach (var listener in networkListeners)
{
if (waitingForConnectionsToken.IsCancellationRequested) break;
if (!listener.Active)
{
continue;
}
if (listener.Pending())
{
try
{
TcpClient connection = await listener.AcceptTcpClientAsync();
//RETAIN CONNECTIONS IN A LIST
connections.Add(connection);
}
catch (ObjectDisposedException x)
{
Write.Error(x.ToString());
}
}
}
}
}));
//ITERATE OVER CONNECTIONS
foreach (var connection in connections)
{
//INVOKE EVENT
OnPendingConnection?.Invoke(this, connection);
}
//CLEAR THE LIST
connections.Clear();
//RESTART THE TASK
if(!waitingForConnectionsToken.IsCancellationRequested)
WaitForConnectionsAsync();
}
So Basically i am catching all pending connections into a list, once the work has completed, I run over the list, fire the event with each connection, clear the list and then start the task again. 因此,基本上,我将所有未决的连接捕获到一个列表中,一旦工作完成,我将遍历该列表,使用每个连接触发事件,清除列表,然后再次启动任务。 This code change doesn't throw the Cross-Thread exception anymore.
此代码更改不再引发Cross-Thread异常。
An improvement i could add now is to accept a collection in the event instead a single connection. 我现在可以添加的一项改进是在事件中接受一个集合,而不是单个连接。
If you have any improvements or better practice suggestions, please let me know. 如果您有任何改进或更好的做法建议,请告诉我。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.