I'm having some trouble in updating a WPF UI from another Thread.
The second Thread is a loop that constantly read messages from a StreamReader.
In these messages there are commands that update the UI.
I have no idea how to do. I read articles about similar problems but were not the same
WPF interface:
private void Button_Click(object sender, RoutedEventArgs e)
{
Thread threadConsume = new Thread(pre_sub);
threadConsume.Start();
}
Other thread:
private void pre_sub()
{
Subscribe();
}
public async Task Subscribe()
{
while (true)
{
try
{
Console.WriteLine("Establishing connection");
using (var streamReader = new StreamReader(await _client.GetStreamAsync(_urlSubscription)))
{
while (!streamReader.EndOfStream)
{
var stream = await streamReader.ReadLineAsync();
if (stream.ToString() == "update")
{
//update the WPF UI
}
}
}
}
catch (Exception ex)
{
}
}
}
Do not create a Thread when you already make async calls.
Simply call and await the async method in an async event handler. The UI updates would thus be made in the UI thread. No Dispatcher calls would be necessary.
private async void Button_Click(object sender, RoutedEventArgs e)
{
await Subscribe();
}
Besides that, you would obviously need some mechanism that stops the infinite loop in the Subscribe method.
You need to use BeginInvoke method. Something in the line of:
.....
while (!streamReader.EndOfStream)
{
var stream = await streamReader.ReadLineAsync();
if (stream.ToString() == "update")
{
var dispatcher = Application.Current.MainWindow.Dispatcher;
dispatcher.BeginInvoke(new Action(() =>
{
//update the WPF UI
}), (DispatcherPriority)10);
}
}
break; ///if UI Updated exit the while true loop
.....
Also, as a side note, don't every swallow exceptions. Log or/and handle the exception on catch block
You have to call dispatcher in order to update the UI
Dispatcher.BeginInvoke(() =>
{
//Update the UI
});
Related
I am new to Async and Await and have created a simple project in order to understand how it works.
For this, I have a simple Windows Form application that has 2 elements:
Get Completed Items button
TextBox showing all Completed Items retrieved
When I click the button, it should display all completed Items in the TextBox.
This is the code I have written:
private async void btnGetCompletedItems_Click(object sender, EventArgs e)
{
QueueSystem queueSystem = QueueSystem.NewInstance(75);
Stopwatch watch = new Stopwatch();
watch.Start();
await Task.Run(() => GetCompletedItems(queueSystem));
watch.Stop();
lblTime.Text = $"{watch.ElapsedMilliseconds.ToString()} ms";
}
private void GetCompletedItems(QueueSystem queueSystem)
{
foreach (var item in queueSystem.GetCompletedItems())
{
txtItems.Text += $"{txtItems.Text}{item.ItemKey}{Environment.NewLine}";
}
}
However, I am getting an error in
txtItems.Text +=
$"{txtItems.Text}{item.ItemKey}{Environment.NewLine}";
The error says
Additional information: Cross-thread operation not valid: Control
'txtItems' accessed from a thread other than the thread it was created
on.
I checked in Debug and a new thread was created for GetCompletedItems(). When I read about Async and Await, I read that it doesn't necessarily create a new thread but it seems to have created a new one for some reason.
Is my implementation and understanding of Async and Await wrong?
Is it possible to use Async and Await in a Windows Forms application?
You cannot access UI thread on a different thread. This should help
private async void btnGetCompletedItems_Click(object sender, EventArgs e)
{
QueueSystem queueSystem = QueueSystem.NewInstance(75);
Stopwatch watch = new Stopwatch();
watch.Start();
var results = await Task.Run(() => queueSystem.GetCompletedItems());
foreach (var item in results)
{
txtItems.Text += $"{txtItems.Text}{item.ItemKey}{Environment.NewLine}";
}
watch.Stop();
lblTime.Text = $"{watch.ElapsedMilliseconds.ToString()} ms";
}
You can access the thread from another thread in a following way. It does helps to avoid the cross thread exception in your application.
private void Thread()
{
this.Invoke((System.Action)(() => {
//your thread call or definition
});
}
When I read about Async and Await, I read that it doesn't necessarily create a new
thread
This is true for regular async methods. Consider this:
private async void button1_Click(object sender, EventArgs e)
{
Trace.WriteLine(Thread.CurrentThread.ManagedThreadId);
await DoesNothing();
}
private async Task DoesNothing()
{
// outputs the same thread id as similar line as from above;
// in particlar, for WinForms this means, that at this point
// we are still at UI thread
Trace.WriteLine(Thread.CurrentThread.ManagedThreadId);
await Task.Delay(1);
}
but it seems to have created a new one for some reason
This is what Task.Run is intended for:
Queues the specified work to run on the ThreadPool
In other words, it pushes anything you pass it as a delegate to a thread pool thread. Since we are in WinForms, this means, that anonymous method () => GetCompletedItems(queueSystem) will be executed at thread pool thread, not at UI one.
Here's code sample from above with little change:
private async void button1_Click(object sender, EventArgs e)
{
Trace.WriteLine(Thread.CurrentThread.ManagedThreadId);
await Task.Run(DoesNothing);
}
private async Task DoesNothing()
{
// outputs DIFFERENT thread id;
// in particlar, for WinForms this means, that at this point
// we are not at UI thread, and we CANNOT access controls directly
Trace.WriteLine(Thread.CurrentThread.ManagedThreadId);
await Task.Delay(1);
}
I've created a simple thread controller class managing the thread's execution, here its code:
public class ThreadController {
int waitCount;
Thread myThread;
public ThreadController() {
//
}
public void StartThread() {
waitCount = 0;
// launch a thread to show an alert when conditions are met!
myThread = new Thread(new ThreadStart(ThreadAction));
myThread.IsBackground = true;
myThread.Start();
}
// method is async as it call an async method itself!
void ThreadAction() {
while (myThread.IsAlive) {
Thread.Sleep(5000);
bool doStop = DoStopTest().Result; // some async function testing stop criterion
if (doStop) {
MainForm.BeginInvoke(new MethodInvoker(delegate() {
MessageBox.Show("Thread stopped!");
}));
//
myThread.Abort();
}
++waitCount;
if (waitCount >= 15) {
myThread.Abort();
}
Thread.Sleep(5000);
}
}
}
Now, I want to make sure the above created threads (there might be several) are killed when I close the MainForm, which I read should be done in the FormClosing event as follows:
void Main_FormClosing(object Sender, FormClosingEventArgs e) {
// unfortunately, an error is thrown when I call following line...
Environment.Exit(Environment.ExitCode);
}
The Environment.Exit call actually generates some weird exceptions... Sometimes a "vhost32.exe stopped working", sometimes an error System.ComponentModel.Win32Exception (0x80004005): Error creating window handle or other painting events that use "Invalid Parameters"...
Am I missing something here? What is the suggested way to cleanly close the form with all associated threads, without running into errors?
The code would be a lot clearer if you used tasks and async/await. DoStopTest() seems to return a Task already, so there's no need to use a raw Thread.
The code could be something as simple as a loop :
public async Task MyTestAndWait()
{
await Task.Delay(5000);
var waitCount=0;
while( waitCount++ < 15 && !(await DoStopTest()))
{
await Task.Delay(10000);
}
MessageBox.Show("Thread stopped!");
}
After each call to await execution resumes on the original synchronization context. For desktop applications, that's the UI thread. That means there's no need to use BeginInvoke
Threads should not be aborted. The correct way is to check a thread-safe signal, like a ManualResetEvent that's raised when a thread needs to exit. When signalled, the thread's code itself should exit.
Using a lot of events can get a bit messy which is why .NET 4.5 added the CancellationToken and CancellationTokenSource classes that can be used to notify both threads and Tasks they need to cancel and exit gracefully.
public async Task MyTestAndWait(CancellationToken ct,int initialDelay,int pollDelay)
{
await Task.Delay(initialDelay,ct);
var waitCount=0;
while(!ct.IsCancellationRequested && waitCount++ < 15 && !(await DoStopTest()))
{
await Task.Delay(pollDelay,ct);
}
MessageBox.Show("Poll stopped!");
}
This will cancel the delays and the loop but it won't cancel the call to DoStepTest(). That method will have to accept a CancellationToken parameter as well
CancellationTokens are created by CancellationTokenSource classes. One of the overloads accepts a timeout, which could be used to cancel the overall operation :
public async void SendSMS_Click(object sender, EventArgs args)
{
var cts=new CancellationTokenSource(TimeSpan.FromMinutes(15));
await MyTestAndAwait(cts.Token,5000,10000);
}
The cts could be stored in a field, to allow cancellation due to another event like a button click :
CancellationTokenSource _cts;
public async void SendSMS_Click(object sender, EventArgs args)
{
SendSMS.Enabled=false;
Cancel.Enabled=true;
_cts=new CancellationTokenSource(TimeSpan.FromMinutes(15);
await MyTestAndAwait(cts.Token,5000,10000);
_cts=null;
SendSMS.Enabled=true;
Cancel.Enabled=false;
}
public async void Cancel_Click(object sender, EventArgs args)
{
_cts?.Cancel();
}
The same code can be used to signal cancellation when closing the form :
void Main_FormClosing(object Sender, FormClosingEventArgs e)
{
_cts.?Cancel();
}
BTW there's no reason to call Environment.Exit() in the form's Closing or Closed events. Closing the main form will end the application unless there's another thread running.
UPDATE
It looks like the actual question is how to verify that an SMS was sent by polling for its send status. The code in this case would be different, while still using task. The method shouldn't have any reference to the UI so it can be moved to a separate Service-layer class. After all, changing providers shouldn't result in changing UIs
Assuming HttpClient is used, it could look like this :
//In an SmsService class
public async Task<(bool ok,string msg)> SendSmsAsync(string phone,string message,CancellationToken ct)
{
var smsMsg=BuildSmsContent(phone,string);
await _httpClient.PostAsync(smsMsg,ct);
//wait before polling
await Task.Delay(_initialDelay,ct);
for(int i=0;i<15 && !ct.IsCancellationRequested;i++)
{
var checkMsg=CheckStatusContent(phone,string);
var response=await _httpClient.GetAsync(check,ct);
if (ct.IsCancellationRequested) break;
//Somehow check the response. Assume it has a flag and a Reason
var status=ParseTheResponse(response);
switch(status.Status)
{
case Status.OK:
return (ok:true,"Sent");
case Status.Error:
return (ok:failed,status.Reason);
case Status.Pending:
await Task.Delay(_pollDelay,ct);
break;
}
}
return (ok:false,"Exceeded retries or cancelled");
}
This method could be used from a button event :
CancellationTokenSource _cts;
public async void SendSMS_Click(object sender, EventArgs args)
{
DisableSending();
var phone=txtPhone.Text;
var message=txtMessage.Text;
_cts=new CancellationTokenSource(TimeSpan.FromMinutes(15);
var (ok,reason)=await _smsService.SendSmsAsync(phone,message,cts.Token);
_cts=null;
if (ok)
{
MessageBox.Show("OK");
}
else
{
MessageBox.Show($"Failed: {reason}");
}
EnableSending();
}
public void EnableSending()
{
SendSMS.Enabled=true;
Cancel.Enabled=false;
}
public void DisableSending()
{
SendSMS.Enabled=false;
Cancel.Enabled=true;
}
I have a Winforms exe and from a menu I launch a slow-running process as a Task. It should take about 30 seconds to get the data and then show a dialog. Usually it no longer returns. I catch exceptions and nothing appears in the log so I know it's run ok. The form just never appears, and no CPU time seems to be taking up. Yet I run it in the debugger and step through the code and it works fine. Occasionally it does seem to work on a faster PC. What is happening?
private async void inPlayRecordToolStripMenuItem_Click(object sender, EventArgs e)
{
if (!GetClient()) return;
{
await Task.Run(() =>
{
LaunchForm();
});
}
}
private async void LaunchForm()
{
try
{
{
var inPlayView = new InPlayView();
await inPlayView.GetData();
inPlayView.ShowDialog();
}
}
catch (Exception ex)
{
Logger.LogMessage(ex.ToString());
}
}
Do this instead:
private async void inPlayRecordToolStripMenuItem_Click(object sender, EventArgs e)
{
if (!GetClient()) return;
await LaunchForm();
}
private async Task LaunchForm()
{
try
{
var inPlayView = new InPlayView();
await inPlayView.GetData();
inPlayView.ShowDialog();
}
catch (Exception ex)
{
Logger.LogMessage(ex.ToString());
}
}
You don't want Task.Run() for an already async method, and as a general rule, async void is okay for event handlers only, so not the LaunchForm() method.
Also as a comment points out, Task.Run() queues the task to the ThreadPool, so it will end up off the UI thread.
I have used async/await on one of my projects and I can't think of a reason to do a ShowDialog in a task. Not sure if this will work but you may want to change your flow a little bit. This should make it more consistent and possibly easier to debug.
private async void inPlayRecordToolStripMenuItem_Click(object sender, EventArgs e) {
if (!GetClient()) {
return;
}
var playView = await LaunchForm();
if (playView != null) {
playView.ShowDialog();
}
}
private async Task<InPlayView> LaunchForm() {
try {
var inPlayView = new InPlayView();
await inPlayView.GetData();
return inPlayView;
} catch (Exception ex) {
// do cleanup of view if needed
Logger.LogMessage(ex.ToString());
return null;
}
}
I'm working on a project and I've hit a lot of road blocks with the UI.
In this case create a new thread to update the UI so that the UI main thread is left open allowing you to still use the UI.
I'm not sure what the problem with this is. I think that maybe I'm using dispatcher to create thread that instead of creating a thread uses the main thread?? Its also worth noting that my MainWindow is singleton instance.
private void button_Click(object sender, RoutedEventArgs e)
{
if (go)
{
city = textBox.GetLineText(0);
if (city == "exit")
{
Environment.Exit(0);
}
Thread t1 = new Thread(new ThreadStart(
delegate
{
this.Dispatcher.Invoke(DispatcherPriority.Background, new Action(start));
}));
t1.Start();
//await Task.Run(() => start());
}
}
You don't need to create a new thread. When you're using async and await, a new thread will be automatically assigned from the thread pool. Just make your button_Click event handler async as mentioned in code below and call the long running task using await keyword.
private async void button_Click(object sender, RoutedEventArgs e)
{
if (go)
{
city = textBox.GetLineText(0);
if (city == "exit")
{
Environment.Exit(0);
}
await Task.Run(() => start());
}
}
What will happen here is when you click on the button the event will be handled. If the condition is satisfied, the long running task is initiated in this line await Task.Run(() => start());. Then it will return to the UI thread without blocking it ie the UI will still be responsive while another thread is executing the long running process in background.
Please read Asynchronous Programming with async and await (C#) on MSDN.
EDIT :
Since you want to control UI elements in your start() method, use the below approach :
private async void button_Click(object sender, RoutedEventArgs e)
{
if (go)
{
city = textBox.GetLineText(0);
if (city == "exit")
{
Environment.Exit(0);
}
await start();
}
}
private async Task start()
{
await Task.Run(async () =>
{
// long running task here
});
// UI Control code here.
}
I wonder how to make sure that a thread is awaiting for events.
Let say I've a component that raises events:
public delegate void TestHandler(object sender, EventArgs eventArgs);
class Producer
{
public event TestHandler Handler;
public void InvokeHandler(EventArgs eventargs)
{
var handler = Handler;
if (handler != null) handler(this, eventargs);
}
public Producer()
{
Task.Factory.StartNew(() =>
{
while (true)
{
Thread.Sleep( (new Random()).Next(0,100) );
InvokeHandler(new EventArgs());
} }); } }
And a Listener:
class Listener
{
private readonly BlockingCollection<EventArgs> _blockingCollection;
public Listener()
{
var producer = new Producer();
_blockingCollection = new BlockingCollection<EventArgs>(10);
producer.Handler += producer_Handler;
}
void producer_Handler(object sender, EventArgs eventArgs)
{
_blockingCollection.TryAdd(eventArgs); //no error handling for simplicity sake
}
internal void ProcessEvents()
{
while (true)
{
EventArgs eventArgs;
try
{
if (_blockingCollection.TryTake(out eventArgs))
Console.WriteLine("Received event");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
} } } }
If I would start it as:
class Program
{
static void Main(string[] args)
{
var listner = new Listener();
listner.ProcessEvents();
} }
I got expected behaviour (from time to time I'm getting the event through blocking collection.
However if I would wrap this call in Task:
Task.Factory.StartNew(() => {
var listner = new Listener();
listner.ProcessEvents(); });
I will never got into the processing part.
Any idea why it would be? Do I miss something obvious?
On the side, does someone know a good description of a pattern that will help here?
I'm looking to pick-up events on one thread (preferably not the main application thread), then process them on a separate thread at the same time blocking if the number of events is getting too high (typical thresholdingI assume)
Thanks in advance,
Stupid me...
When I wrap my call in Task it is run on a Background thread.
I should have wait for the task to finish before quiting.
Having done that, that is assigning:
var task = Task.Factory.StartNew(/*task body*/);
task.Wait();
and waiting I have got what I want.
In order to figure out what's going on you should put some logging messages and verify that no exceptions are thrown when when you start the new task:
Thread listenerThread = new Thread(() =>
{
// print a message when the task is started
Console.WriteLine("Task started!");
var listner = new Listener();
listner.ProcessEvents();
});
// set your thread to background so it doesn't live on
// even after you're
listenerThread.IsBackground = true;
listenerThread.Start();
Thread.Join() does the same thing as Task.Wait(). You can call it without specifying a timeout, you can specify an infinite timeout or if you want your test to stop waiting for the thread to finish, then you specify a timeout in milliseconds:
// this will block until the thread is complete
listenerThread.Join(Timeout.Infinite);
If you need to stop the test, then you have to perform an asynchronous interrupt. Interrupt raises a ThreadInterruptedException inside the Thread which you're supposed to catch (as shown in the ProcessEvents method):
listenerThread.Interrupt();
Additionally, you're using a blocking collection, but you're not utilizing the blocking functionality of the collection. There is no reason why you should keep retrying when there is nothing in the collection, instead you would rather block until there is something in it (which is what this collection is designed to do):
internal void ProcessEvents()
{
while (true)
{
try
{
var myEvent = _blockingCollection.Take();
Console.WriteLine("Received event");
}
catch(ThreadInterruptedException)
{
Console.WriteLine("Thread interrupted!");
break;// break out of the loop!
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
// Do you REALLY want to keep going if there is an unexpected exception?
// Consider breaking out of the loop...
}
}
}