简体   繁体   中英

Raising events during Tasks causing Cross-Thread Exception

I wrote a Client/Server async classes which works fine in the console. 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. 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?

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.

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.

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.

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:

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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