简体   繁体   English

在任务期间引发事件导致跨线程异常

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM