简体   繁体   中英

Hot to call a method from another thread or fire a event from another thread which gets handled on the main thread

I know, the title might be confusing, but I can't really express it better in short.

Basically, I'm writing a TCP Server. I don't have a Windows Form to show, the only thing the user sees is a TrayIcon.

My TCP Server class creates a thread for listening for clients, and then additional thread for every client handling communication. When all communication is done, I want to call a method on the main thread.

I've done it by firing a event from the client communication thread which gets handled on the main thread, and all worked fine until I wanted to add desktop notifications to my application. I've build a notification using WPF ( iControlNotification ) and wanted to show it in the event handler I mentioned before, but I'm getting a error message saying something like "The calling thread has to be a STA Thread".

Here's some code (I removed unnecessary party):

static class Program {

    [...]

    [STAThread]
    static void Main() {
        Log("iControlServerApplication started.");
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        trayIcon = new TrayIcon();
        trayIcon.Display();

        NotificationManager = new iControlNotificationManager();

        server = new TCPServer();
        server.CommandReceived += new TCPServer.CommandReceivedEventHandler(tcpServer_CommandReceived);
        if (server.Start()) {
            NotificationManager.ShowNotfication("iControl Server Application", "Server started. " + plugins.Count + " plugins loaded.");
            Application.Run();
        } else {
            MessageBox.Show("Port " + server.Port + " is already in use. Server could not be started.", ApplicationName, MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }

    [...]

    static void tcpServer_CommandReceived(object source, TCPServer.CommandReceivedEventArgs e) {
        string toolTipText = "[" + e.Client.IPAddress + "] >> " + e.Command;
        NotificationManager.ShowNotfication("iControl Server Application", toolTipText);

        foreach (IiControlPlugin plugin in plugins) {
            plugin.Handle(e.SplittedCommands, e.Client);
        }
    }

    [...]
}

-

class TCPServer {

    public delegate void CommandReceivedEventHandler(object source, CommandReceivedEventArgs e);
    public event CommandReceivedEventHandler CommandReceived;
    public class CommandReceivedEventArgs : EventArgs {
        private string _command;
        private string[] _splittedCommands;
        private iControlClient _client;
        public CommandReceivedEventArgs(string command, iControlClient client) {
            _command = command;
            _splittedCommands = command.Split(new Char[]{' '});
            _client = client;
        }
        public string Command { get { return _command; } }
        public string[] SplittedCommands { get { return _splittedCommands; } }
        public iControlClient Client { get { return _client; } }
    }

    public TCPServer() {
        this.tcpListener = new TcpListener(IPAddress.Any, Port);
        this.icClients = new Dictionary<String, iControlClient>();
    }

    public Boolean Start() {
        if (PortIsAvailable(Port)) {
            this.listenThread = new Thread(new ThreadStart(ListenForClients));
            this.listenThread.Start();
            Program.Log("ListeningThread started.");
            return true;
        } else {
            return false;
        }
    }

    private void ListenForClients() {
        this.tcpListener.Start();

        TcpClient client;

        while (this.keepListening) {
            try {
                client = this.tcpListener.AcceptTcpClient();
            } catch {
                break;
            }

            iControlClient icClient = new iControlClient(client);
            icClient.Thread = new Thread(new ParameterizedThreadStart(HandleClientCommunication));
            icClient.Thread.Start(icClient);
        }
        Program.Log("Stop listening.");
    }

    private void HandleClientCommunication(object client) {
        iControlClient icClient = (iControlClient)client;
        NetworkStream clientStream = icClient.TCP.GetStream();

        clientStream.ReadTimeout = 10;

        int bufflen = 4096;
        byte[] message = new byte[bufflen];
        int bytesRead;

        while (this.keepReceiving && icClient.keepConnected) {
            bytesRead = 0;

            try {
                bytesRead = clientStream.Read(message, 0, bufflen);
            } catch {
                break;
            }

            if (bytesRead == 0) {
                break;
            }
            ProcessReceivedData(icClient, ParseData(message, bytesRead));
        }

        Program.Log("[" + icClient.IPAddress + "] Connection closed.");

        icClient.TCP.Close();
        this.icClients.Remove(icClient.IPAddress);
    }

    private void ProcessReceivedData(iControlClient icClient, String[] commands) {
        Program.Log("[" + icClient.IPAddress + "] >> " + String.Join(" ", commands));

        if (this.CommandReceived != null) {
            CommandReceived(this, new CommandReceivedEventArgs(String.Join(" ", commands), icClient));
        }

        NetworkStream clientStream = icClient.TCP.GetStream();
        ASCIIEncoding encoder = new ASCIIEncoding();
        byte[] buffer = encoder.GetBytes("::ok");
        clientStream.Write(buffer, 0, buffer.Length);
        clientStream.Flush();

        icClient.keepConnected = false;
    }
}

-

public class iControlNotificationManager {
    private iControlNotifications _notifications;

    public void ShowNotfication(string caption, string message) {
        if ((Boolean)Program.GetSetting("notifications", true) == false) return;
        Dispatcher.CurrentDispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(
        () => {
            iControlNotification noti = new iControlNotification(caption, message);
            noti.Show();
        }));
    }
}

-

public class iControlNotification : Window {

    private iControlNotificationModel _notification;

    public iControlNotification(string caption, string message) {  // Here's the error
        InitializeComponent();

        _notification = new iControlNotificationModel() {
            Caption = caption,
            Message = message
        };

        this.DataContext = _notification;
    }
}

So how should I call tcpServer_CommandReceived so that the Notification Window can be shown the right way?

I'm really stuck here, I really appreciate any help on this!

//How to call a method from another thread :

a) You can invoke it in other thread by passing SynchronizationContext object to it:

void Method(object s) 
{
SynchronizationContext sync = s as SynchronizationContext;
sync.Post(delegate { // what to do in other thread}, null);
}

Then in code you run this method in new task, passing your sync context as object (for example):

Task t = Task.Factory.StartNew(Method, SynchronizationContext.Current);

b) You can create extension method for this purpose (here is example that i used in win forms application to update UI):

   public static class ControlExtensions
    {
        /// Executes the Action asynchronously on the UI thread, does not block execution on the calling thread.

        public static void UIThread(this Control @this, Action code)
        {
            if (@this.InvokeRequired)
            {
                @this.BeginInvoke(code);
            }
            else
            {
                code.Invoke();
            }
        }
    } 

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