Tell a window to close from a thread in C# - c#

I am working in WPF and I have a dialog window that start a listen socket, and is supposed to close as soon as someone connect. Here is my naive, non working snippet code:
void acceptCallback(IAsyncResult iar)
{
socket = listenSocket.EndAccept(iar);
DialogResult = true; // error here
Close();
}
private void ValidButton_Click(object sender, RoutedEventArgs e)
{
IPEndPoint iep = new IPEndPoint(IPAddress.Any, port);
listenSocket.Bind(iep);
listenSocket.Listen(1);
listenSocket.BeginAccept(acceptCallback, null);
}
I get an error telling me that DialogResult can't be accessed from this thread, I understand that my "acceptCallback" function is called from the thread running the accept asynchronously, but don't really know how to get the behavior I want.
How can I tell the main thread from this callback that it is supposed to close the dialog window in a proper way ?

You can usually access UI elements (as the dialog window object) only from the UI thread.
That is simply done by using the Dispatcher of the UI element:
void acceptCallback(IAsyncResult iar)
{
socket = listenSocket.EndAccept(iar);
Dispatcher.Invoke(() =>
{
DialogResult = true;
Close();
});
}
In case it is .NET 4.0 or below, you have to explicitly create an Action from the lambda expression:
Dispatcher.Invoke(new Action(() =>
{
DialogResult = true;
Close();
}));
or
Dispatcher.Invoke((Action)(() =>
{
DialogResult = true;
Close();
}));

Related

How to close WPF window inside of a Task

I need to run a task to check the connection.
My windows should not be frozen during this check.
So I start the Task, and close my window at the end of this task.
But this returns an exception: InvalidOperationException:'The calling thread cannot access this object because another thread owns it'.
Like this :
private void Window_ContentRendered(object sender, System.EventArgs e)
{
Task.Run(() =>
{
ConnectionState = false;
if (NetworkTools.CheckGlobalConnection() == (ConnectionStatus.NetworkConnectionSuccess, ConnectionStatus.ServerConnectionSuccess))
{
ConnectionState = true;
}
this.Close();
});
}
How do I close my window at the end of the task without freezing it and without having this exception ?
(I have a progress bar that rotates indefinitely)
Or you could just use async await. Task.Run will offload and the await will create a continuation on current SynchronizationContext. In turn giving control back to the UI and closing on the right thread.
private async void Window_ContentRendered(object sender, System.EventArgs e)
{
await Task.Run(() =>
{
ConnectionState = false;
if (NetworkTools.CheckGlobalConnection() == (ConnectionStatus.NetworkConnectionSuccess, ConnectionStatus.ServerConnectionSuccess))
ConnectionState = true;
});
this.Close();
}
Also as noted, Calling ConfigureAwait(false), would not be the right thing to do in this case
Use Dispatcher to queue window closing logic on the unique UI thread.
Something like
Dispatcher.Invoke(
() =>
{
// close window here
});
Whatever is passed into .Invoke(...) as a delegate, is invoked on the UI thread and hence has right to access all UI elements. It is common (and the only correct) way to deal with UI-mutations within non-UI threads.
As an alternate method you can use ContinueWith
private void Window_ContentRendered(object sender, System.EventArgs e)
{
Task.Run(() =>
{
// Your code
}).ContinueWith((tsk) =>
{
this.Close();
}, TaskScheduler.FromCurrentSynchronizationContext());
}

Cannot start a simple TCP/IP client

I'm trying to open a TCP/IP listener but when I run the code below, it crashes. It doesn't give me an error because when it crashes, it freezes and stops responding.
TcpListener server = new TcpListener(IPAddress.Any, 619);
TcpClient client;
private void button3_Click(object sender, EventArgs e)
{
server.Start();
client = server.AcceptTcpClient();
if (client.Connected)
{
MessageBox.Show("connected");
}
}
I know for a fact this port is free so that's not it. it crashes on this line:
client = server.acceptTcpClient();
You're executing a blocking call on the UI thread which gives the appearance of a "Crashing" application.
You need to use another thread or do things asynchronously. Background worker might be a starting point.
private BackgroundWorker bgw = new BackgroundWorker();
public MainWindow()
{
InitializeComponent();
bgw.DoWork += Bgw_DoWork;
bgw.RunWorkerCompleted += Bgw_RunWorkerCompleted;
bgw.WorkerSupportsCancellation = true;
}
private void button3_Click(object sender, EventArgs e)
{
if (!bgw.IsBusy)
{
bgw.RunWorkerAsync();
((Button)sender).Content = "Cancel";
}
else
{
bgw.CancelAsync();
}
}
private void Bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
button.Content = "Start";
}
private void Bgw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
var server = new TcpListener(IPAddress.Any, 619);
server.Start();
while (true)
{
if (worker.CancellationPending)
{
e.Cancel = true;
server.Stop();
break;
}
else
{
if (server.Pending())
{
var client = listener.AcceptTcpClient();
// handle client here or do something like below to return the client to the RunWorkerCompleted method in
// e.result
e.Result = client;
break;
}
}
}
}
There are other options such as using server.AcceptTcpClientAsync() instead of polling the server.Pending() property. Also, polling in this way without using Thread.Sleep in between may be overkill, i.e., you're checking the property very frequently and wasting cycles.
Following all of that, you need to figure out a way to deal with the client in a way that makes sense for your application. One click per client accepted? Handle new connections forever as they arrive until you cancel the listener? Etc. Good luck
BackgroundWorker tutorial
AcceptTcpClientAsync MSDN
This is expected behaviour. Your program hasn't crashed - the call to AccpetTcpClient() blocks waiting for an incoming connection:
https://msdn.microsoft.com/en-us/library/system.net.sockets.tcplistener.accepttcpclient(v=vs.110).aspx
You need to also open a TCP connection to port 619 from another thread, then your program will continue to run.
AcceptTcpClient is a blocking method
(https://msdn.microsoft.com/en-us/library/system.net.sockets.tcplistener.accepttcpclient%28v=vs.110%29.aspx).
When you call AcceptTcpClient, it will wait until a connection is made. Because it waits on the main thread (where also the GUI is running on), it will look like the program is frozen. Try using AcceptTcpClientAsync or putting the accepting part in a different thread. That should solve your problem.
Desirius

How to get update from BackgroundWorker.DoWork Event

In my login window, when I click the login button, the configuration and login processes will be executed and those methods are in another class file. So far what I've achieved is when I clicked the login button, the loading animation will be displayed on top of the login window and those processes will be executed as well. There are some login error checking in the configuration class file, so when login error caught, a message box with relevant info will be prompted out and stop the login process, the problem is the message box will not be prompted out since I put those config and login process in the BackgroundWorker.DoWork event.
Here's the codes for Login.xaml.cs:
private void LoginBtn_Click(object sender, RoutedEventArgs e)
{
Loading.Visibility = Visibility.Visible; //The loading animation
Loading.Visibility = Visibility.Visible;
Cursor = Cursors.Wait;
bw.DoWork += new DoWorkEventHandler(LoginInfoVerification);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(RunWorkerCompleted);
bw.WorkerReportsProgress = true;
bw.RunWorkerAsync();
}
private void LoginInfoVerification(object sender, DoWorkEventArgs e) {
var loginInfoVerification = config.ServerConnection(loginInfo.userName,
loginInfo.galPassword, loginInfo.place,
loginInfo.host, loginInfo.port,
loginInfo.application);
}
private void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (GlobalVariables.loginSuccess == true)
{
//Pass these variables to main window
var mainWindow = new MainWindow(loginInfo.userName, loginInfo.place, loginInfo.host, loginInfo.port,
loginInfo.application);
mainWindow.Show();
this.Close();
}
else
Loading.Visibility = Visibility.Collapsed;
Cursor = Cursors.Arrow;
}
For Configuration.cs:
public Configuration ConfigServerConnection(string loginUserName, string loginPassword, string loginPlace, string loginHost, int loginPort, string loginApplication)
{
//Server connection
//Login info verification
//If login error caught, prompt message box, different errors, different message
}
Is there any better suggestion in handling UI update and process update at the same time for my case? Please help.
To display the message box you need to switch back to the UI thread with Dispatcher.Invoke, see this.
Application.Current.Dispatcher.Invoke(() => /* show appropriate message box */);
Alternatively if you are using .NET 4.5 or higher you can make your life a lot easier with async-await by marking LoginBtn_Click with the async keyword and then awaiting the log in process.
If there is an asynchronous version of ServerConnection that returns a task you can just await that otherwise you can use Task.Run() to execute ServerConnection on a thread pool thread.
Await will kick off the log in operation asynchronously and once complete will resume the rest of the method on the GUI thread so you can manipulate GUI components without using Dispatcher.Invoke.
private async void LoginBtn_Click(object sender, RoutedEventArgs e)
{
Loading.Visibility = Visibility.Visible; //The loading animation
Loading.Visibility = Visibility.Visible;
Cursor = Cursors.Wait;
LoginVerification loginInfoVerification = null;
await Task.Run(() =>
{
loginInfoVerification = config.ServerConnection(loginInfo.userName,
loginInfo.galPassword,
loginInfo.place,
loginInfo.host,
loginInfo.port,
loginInfo.application);
});
.. rest of code, check login success, show message box..
}

c# Thread won't add Row to DataGridView CrossThreadError

I am learning c# and I would like to know why my code won't add a row to my DataGridView in the ReceivePacket area. It works in the SendPacket but not in the other.
The purpose is to simply send a UDP packet to a machine, thanks in advance.
Here is my code:
private void btnSend_Click(object sender, EventArgs e)
{
SendPacket();
}
private void btnReceiving_Click(object sender, EventArgs e)
{
Thread thread = new Thread(new ThreadStart(reciev));
thread.Start();
}
UdpClient client = new UdpClient();
public void SendPacket()
{
byte[] packet = Encoding.ASCII.GetBytes(DateTime.Now.ToString("HH:mm:ss:ff"));
client.Send(packet, packet.Length, tbIP.Text, 444);
dgvSend.Rows.Add(DateTime.Now.ToString("HH:mm:ss:ff"));
}
public void ReceivePacket(byte[] packet)
{// it goes wrong here, because it gives a crossthread error
dgvReceiv.Rows.Add(Encoding.ASCII.GetString(packet), DateTime.Now.ToString("HH:mm:ss:ff"));
}
public void reciev()
{
UdpClient client = new UdpClient(444);
while (true)
{
IPEndPoint server = new IPEndPoint(IPAddress.Any, 0);
byte[] packet = client.Receive(ref server);
ReceivePacket(packet);
}
}
I fixed it using this line of code instead of the normal (with the invoke :) ):
dgvReceiv.Invoke(new MethodInvoker(delegate { dgvReceiv.Rows.Add(Encoding.ASCII.GetString(packet), DateTime.Now.ToString("HH:mm:ss:ff")); }));
Here you go example of using delegates to invoke actions from other threads on controls created on main thread.
http://msdn.microsoft.com/en-us/library/zyzhdc6b.aspx
The problem is that you're attempting to create a GUI element (a form) on another thread than the main GUI thread. You can only create GUI elements on the GUI thread. Anything else doesn't work.
You should receive the data in your secondary thread, and then copy it over to your GUI thread, to put it inside a GUI element. I think there's a function Control.Invoke that is a delegate that will invoke a function on the thread the Control was created on, which you could use to call from your secondary thread to actually populate your form or whatever control.

Background worker, and cross thread issue

I have a Winforms application which is working fine.. using a BackgroundWorkerThread to manage GUI usability during processing of serial data to a device.
It's working fine.
Now, I am adding a new method, and copying what I did in other forms. But I am getting a cross thread exception.
I declare my BWT like this:
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerSupportsCancellation = true;
bw.DoWork += DownloadGpsDataFromDevice;
bw.WorkerReportsProgress = true;
bw.RunWorkerAsync();
I then have a method delared like this, which does the background work:
private void DownloadGpsDataFromDevice(object sender, DoWorkEventArgs e)
{
_performScreenUpdate = true;
tsStatus.Text = "Downloading GPS Data...";
Invalidate();
Refresh();
Common.WriteLog("Extracting raw GPS data. Sending LL.");
ReplyString raw = DeviceServices.ExecuteCommand("$LL");
The DeviceServices.ExecuteCommand("$LL"); is the bit that does the work, but I am getting the exception on the previous line, where I log to a text file. Now, that makes you worry - writing to a file. However, I have done this thousands of times in another BWT.
I made the writing thread safe. Here this my Common.WriteLog method:
public static void WriteLog(string input)
{
lock (_lockObject)
{
WriteLogThreadSafe(input);
}
}
private static void WriteLogThreadSafe(string input)
{
Directory.CreateDirectory(LogFilePath);
StreamWriter w = File.AppendText(LogFilePath + #"\" + LogFileName);
try
{
w.WriteLine(string.Format("{0}\t{1}", DateTime.Now, input));
}
catch (Exception e)
{
System.Console.WriteLine("Error writing to log file!");
System.Console.WriteLine("Tried to write: [" + input + "]");
System.Console.WriteLine("Failed with error: [" + e.Message + "]");
}
finally
{
w.Close();
}
}
This have been working for ages. I don't believe the error is there. I think I am just missing something on the call maybe?
You cannot change UI elements from BackgroundWorker thread. You'll have to marshall back to UI thread by calling Invoke().
Try this
private void DownloadGpsDataFromDevice(object sender, DoWorkEventArgs e)
{
_performScreenUpdate = true;
Invoke((MethodInvoker)(() => {
tsStatus.Text = "Downloading GPS Data...";
Invalidate();
Refresh();
});
...
The issue is that you are updating UI elements from non-UI thread:
Those lines should not be inside of DownloadGpsDataFromDevice
tsStatus.Text = "Downloading GPS Data...";
Invalidate();
Refresh();
To take advantage of BackgroundWorker run method bw.ReportProgress(0);. Update UI in ProgressChanged handler, which was specifically designed for this purpose.
void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (e.ProgressPercentage = 0)
{
tsStatus.Text = "Downloading GPS Data...";
Invalidate();
Refresh();
}
}
Some instances can't or should not be accessed by multiple threads. You have two options to protect your data from cross thread exceptions.
You can lock your object when you access it from multiple threads with a lock:
object locker = new object();
SomeObject MyObject = new SomeObject();
private void FromMultipleThread()
{
lock(locker)
{
MyObject = OtherObject;
}
}
Your second option is to lock your thread with a ManualResetEvent. It is very simple, you only have to call WaitOne() from you ManualResetEvent to lock your thread while an other thread access your "cross threaded" Object.
In your case, you would want to change your UI from the reportProgress of your backgroundWorker. The reportProgress will come back to the initial thread, then you can modify your UI.
Are you sure that is the right line? I don't think you should be able to update the ui in your worker. Try commenting out the gui update and clean and build your solution to see if the logging is really the problem. To update the ui, set WorkerReportsProgress and create an event handler for that to update the ui and report progress in the worker.

Categories