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

时间:2018-10-22 04:32:14

标签: c# async-await

我编写了一个Client / Server异步类,该类在控制台中可以正常工作。我为服务器创建了一个WinForm项目,该项目订阅了存在.Pending()连接时服务器抛出的事件,并将一些消息写入文本框-这会导致跨线程异常。异常不会令我感到惊讶,但是我正在寻找一种方法来调用该事件而不会导致此异常,而不用.InvokeRequired.Invoke在GUI / Control上对其进行处理-如果可能的话?

服务器启动如下:

Server server = new Server(PORT);
server.RunAsync();

.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());

    }

下面的代码在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();
        };

这是无限期运行的代码,直到触发取消令牌:

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());
                        }

                    }
                }
            }
        }));
    }

我知道我可以在GUI上使用文本框.InvokeRequired.Invoke,但是我感觉服务器应该以GUI不会引起跨线程异常的方式抛出事件。

是否有一种方法可以在该“无限任务”中调用事件处理程序而不会导致此异常?

1 个答案:

答案 0 :(得分:0)

多亏了评论和充足的睡眠,我通过将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();
    }

因此,基本上,我将所有未决的连接捕获到列表中,一旦工作完成,我将遍历该列表,使用每个连接触发该事件,清除列表,然后再次启动任务。此代码更改不再引发Cross-Thread异常。

我现在要添加的一项改进是在事件中接受一个集合,而不是单个连接。

如果您有任何改进或更好的实践建议,请告诉我。