Why Task Parallel Library can update UI simply? - c#

When I try update UI using Task in .net Framework 4, I found out that something strange. I never thought UI thread can be updated from Task Library. I just wanted to test it and amazingly it works. Here is my code, can someone explain how it works ?
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
int i=0;
Task myTask = new Task(() =>
{
while (true)
{
label1.Text = "Hello" + i++;
Thread.Sleep(3000);
};
});
myTask.Start();
}

The fact that you didn't get an exception this time doesn't mean that you (or even worse your customer) won't get an exception the next time. You were just lucky. Make sure you marshal all function calls to the UI on the main thread. Or if you want to spare this task use a BackgroundWorker which will take care of executing the callback on the main thread.

Related

C# backgroundworker RunworkerCompleted vs async await

Updated with answers:
The true way of wait until a number of different tasks to be finished would need async await instead of background worker.
#
I know there are numerous discussion about backgroundworker but I've being searched around and cannot find the answer.
Here is my code example(basic logic, the actual code is much longer), I wonder if there is a way to get around this:
BackgroundWorker MCIATS1Worker = new BackgroundWorker();
private AutoResetEvent _MCIATS1WorkerResetEvent = new AutoResetEvent(false);
public MainWindow()
{
InitializeComponent();
MCIATS1Worker = new BackgroundWorker();
MCIATS1Worker.DoWork += new DoWorkEventHandler(MCIATS1Worker_DoWork);
MCIATS1Worker.WorkerReportsProgress = true;
MCIATS1Worker.WorkerSupportsCancellation = true;
MCIATS1Worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(MCIATS1_RunWorkerCompleted);
for (int i = 1; i <= 10; i++)
{
//some code
MCIATS1Worker.RunWorkerAsync();
_MCIATS1WorkerResetEvent.WaitOne();
}
}
DoWork and runworkercompleted
void MCIATS1Worker_DoWork(object sender, DoWorkEventArgs e)
{
//do something here
}
void MCIATS1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("hello world");
_MCIATS1WorkerResetEvent.Set();
}
For some reasons, the MCIATS1_RunWorkerCompleted won't be triggered until the loop finished. And apparently the WaitOne is holding the loop.
Here is my question,
why RunWorkerCompleted won't be trigger the RunWorkerCompleted when the worker is actually finished the work?
Thank you.
###UPDATED SOLUTION
This is the right way of doing it.
private async void WhateverFunction()
{
await Task.WhenAll(MCIATS1WorkerDoWorkAsync(param),...other tasks);
}
private Task MCIATS1WorkerDoWorkAsync(bkgWorkParameter param)
{
return Task.Run(() =>
{
//Do whatever
});
}
It happens because when you use a BackgroundWorker it's RunWorkerCompleted event is posted to the SynchronizationContext of the thread that called RunWorkerAsync.
Because you call RunWorkerAsync on the UI thread the event can't run until the UI thread starts processing new messages in the message loop. However you prevented the UI thread from returning to the message loop by your _MCIATS1WorkerResetEvent.WaitOne(); call.
So what it boils down to is _MCIATS1WorkerResetEvent.Set(); is waiting for MCIATS1_RunWorkerCompleted to fire to stop blocking and MCIATS1_RunWorkerCompleted is waiting for _MCIATS1WorkerResetEvent.Set(); to stop blocking the UI thread so it's message to be processed.
Both things are waiting for the other to complete before itself completes and you have a classic deadlock.
There is no need for a for loop for this problem to happen, this same problem would happen with or without out the loop, in fact the loop never gets to run it's 2nd itteration because it will have deadlocked on the first time through so it does not matter that there is a loop at all.
Depend on what kind of work your MCIATS1Worker_DoWork method do, you can consider to use async-await approach, which makes code a little bid more cleaner.
private async Task MCIATS1WorkerDoWorkAsync()
{
await Task.Delay(1000) // do something asynchronously for 1 second
}
private async void MainWindow_Load(object sender, EventArgs e)
{
for (int i = 1; i <= 10; i++)
{
//some code
await MCIATS1WorkerDoWorkAsync();
MessageBox.Show("hello world");
}
}
Message box will be shown 10 times every 1 second. await keyword will continue loop only after MCIATS1WorkerDoWorkAsync method has successfully finished.
With async-await your form will remain responsive and if DoWork method do some IO operations, then you will not start another thread (as BackgroundWorker do) and whole execution will happens on one thread.

Using a BlockingCollection to queue Tasks

I am trying to create a way to queue up Tasks to run, so I have tried to implement it using a BlockingCollection. The problem I find is whenever I try to add the Task, the Task executes. Sample code as below:
private void button1_Click(object sender, EventArgs e)
{
textBox2.Clear();
for (int i = 0; i < 10; i++)
_processCollection.Add(BigTask(i));
}
static BlockingCollection<Task> _processCollection = new BlockingCollection<Task>();
Thread ConsumerThread = new Thread(LaunchConsumer);
private static async void LaunchConsumer()
{
while (true)
{
var processTask = _processCollection.Take();
await Task.Run(() => processTask);
}
}
async Task BigTask(int i)
{
await Task.Delay(5000);
textBox2.AppendText($"Text{i}\n");
}
What seems to happen in debug is all the tasks seem to run as they are added into the blocking collection. I tried switching the blocking collection to use Action, but that just leads to nothing happening. As below (only changes shown):
private void button1_Click(object sender, EventArgs e)
{
textBox2.Clear();
for (int i = 0; i < 10; i++)
{
int iC = i;
_processCollection.Add(async () => await BigTask(iC));
}
}
static BlockingCollection<Action> _processCollection = new BlockingCollection<Action>();
Thread ConsumerThread = new Thread(LaunchConsumer);
private static async void LaunchConsumer()
{
while (true)
{
var processTask = _processCollection.Take();
await Task.Run(processTask);
}
}
I feel like I have made some small error somewhere, because it feels like this should work. I have tried to find someone doing something similar but have had no luck, which makes me think maybe my concept is flawed so feel free to suggest an alternative.
_processCollection.Add(BigTask(i)); doesn't work because this calls BigTask(i) immediately, and when that is called, the work starts.
You were on the right track by wrapping this in a separate BigTask launcher, but by using Action, you don't provide your LaunchConsumer with any means to track the progress. await Task.Run(processTask) will continue pretty much immediately with the next task. You need to use Func<Task> to avoid that.
The reason you don't see any results is likely unrelated. Now that you manage to launch the task from your newly created thread, the call to textBox2.AppendText is no longer done from the UI thread. That's not supported. Only the UI thread can access UI objects. You can use textBox2.Invoke to pass an action back to the UI thread, and that action can then call AppendText.
Tested working code:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
ConsumerThread.Start();
}
private void button1_Click(object sender, EventArgs e)
{
textBox2.Clear();
foreach (var i in Enumerable.Range(0, 10))
_processCollection.Add(() => BigTask(i));
}
static BlockingCollection<Func<Task>> _processCollection = new BlockingCollection<Func<Task>>();
Thread ConsumerThread = new Thread(LaunchConsumer);
private static async void LaunchConsumer()
{
while (true)
{
var processTask = _processCollection.Take();
await Task.Run(processTask);
}
}
async Task BigTask(int i)
{
await Task.Delay(5000);
textBox2.Invoke(new Action(() => textBox2.AppendText($"Text{i}\n")));
}
}
That said, BlockingCollection is not really the best collection type to use here. It dedicates one thread to pretty much nothing but waiting. Also, Task.Run when you're already in a background thread can admittedly sometimes be useful, but doesn't add anything here. What to do instead depends on your needs. Whether all tasks are known beforehand makes a difference. Whether you may want multiple consumers makes a difference. Other things I haven't thought of may also make a difference.

Why does Thread.Sleep() freeze the Form?

I try to experiment with Thread.Sleep(). I created basic Windows Forms application with one button.
private void button1_Click(object sender, EventArgs e)
{
Thread thread1 = new Thread(DoStuff);
thread1.Start();
for (int i = 0; i < 100000; i++)
{
Thread.Sleep(500);
button1.Text +=".";
}
}
public void DoStuff()
{
//DoStuff
}
When I click my button the DoStuff method works fine, but the GUI freezes and nothing happens. Can someone explain me why?
Thread.Sleep just sleeps the current thread (i.e. stops it from doing anything, such as redrawing, processing clicks etc), which in your case is the UI thread. If you put the Sleep in DoStuff you wouldn't experience the block as you'd be on a separate thread although you wouldn't be able to update button1. Depending on the version of .NET you're using consider using the Task Parallel Library, something like this:
private TaskScheduler _uiScheduler;
public Form1()
{
InitializeComponent();
_uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
}
private void button1_Click(object sender, EventArgs e)
{
Thread thread1 = new Thread(DoStuff);
thread1.Start();
// Create a task on a new thread.
Task.Factory.StartNew(() =>
{
for (int i = 0; i < 100000; i++)
{
Thread.Sleep(500);
// Create a new task on the UI thread to update the button
Task.Factory.StartNew(() =>
{ button1.Text += "."; }, CancellationToken.None, TaskCreationOptions.None, _uiScheduler);
}
});
}
To keep the UI active, you need for the main UI thread to service its message pump. It can only do that when it is not handling UI events. In your case the function
private void button1_Click(object sender, EventArgs e)
{
Thread thread1 = new Thread(DoStuff);
thread1.Start();
for (int i = 0; i < 100000; i++)
{
Thread.Sleep(500);
button1.Text +=".";
}
}
does not return for around 100000*500 milliseconds. While this event handler is executing, the UI thread is busy. It is executing this event handler. As such it is not able to service the message pump. Hence your application's UI freezes.
For that you better use a Timer but if you want your current code to work you need to add Application.DoEvents(); after you update the button.Label += "."
If you're new to multithreading, I strongly encourage you to look at the Task Parallel Library (TPL). It simplifies threading, and gives you tools to help guarantee callback (continuation) threads occur on the UI thread.
The TPL is in the System.Threading.Tasks namespace.
Update: just seen your comment about .Net v2. TPL was introduced in .NET v3.5 or possibly as late as v4.
EDIT: After programming for a few more years, I now know how terrible of a practice this is. DO NOT DO ANYTHING I SUGGESTED BELOW. It's all crap. A more proper solution would be doing all of your intensive methods async all together. Regardless, don't do what I mention below.
All The methods above do work however, I do recommend just using an async void.
Sleep() just pauses the current thread for int amount of milliseconds, and if your whole program runs off of 1 thread, it'll pause the whole program. Don't quote me on this, I do believe that async creates a new thread specifically for that function.
Below I've included a better sleep function.
To call the function asleep(milliseconds),
replace "milliseconds" with how many milliseconds you wish to sleep for.
Function Code:
public async void asleep(int time){
await Task.Delay(time)
}
Re-arrange code as following
private void button1_Click(object sender, EventArgs e)
{
Thread thread1 = new Thread(DoStuff);
thread1.Start();
}
public void DoStuff()
{
for (int i = 0; i < 100000; i++)
{
Thread.Sleep(500);
//Invoke goes here
}
}
Now you run your WORK in a separate thread and release your UI thread for usual work (Drawing related or other work)
NOTE - Now you will need Invoke methods to change Button text , else you will get warning for "Cross-thread operation not valid"
More on Invokes - How to update the GUI from another thread in C#?

Update UI from thread in WinRT

Since the Windows 8 consumer preview was released a few days ago, I am working on the new WinRT (for Metro Applications) in C# and I had ported my self written IRC class to the new threading and networking.
The problem is: My class is running an thread for receiving messages from the server. If this happens, the thread is making some parsing and then firing an event to inform the application about this. The subscribed function then 'should' update the UI (an textblock).
This is the problem, the thread cannot update the UI and the invoker method that has worked with .NET 4.0 doesn't seem to be possible anymore. Is there an new workaround for this or even an better way to update the UI ? If I try to update the UI from the event subscriber i will get this Exception:
The application called an interface that was marshalled for a
different thread (Exception from HRESULT: 0x8001010E
(RPC_E_WRONG_THREAD))
The preferred way to deal with this in WinRT (and C# 5 in general) is to use async-await:
private async void Button_Click(object sender, RoutedEventArgs e)
{
string text = await Task.Run(() => Compute());
this.TextBlock.Text = text;
}
Here, the Compute() method will run on a background thread and when it finishes, the rest of the method will execute on the UI thread. In the meantime, the UI thread is free to do whatever it needs (like processing other events).
But if you don't want to or can't use async, you can use Dispatcher, in a similar (although different) way as in WPF:
private void Button_Click(object sender, RoutedEventArgs e)
{
Task.Run(() => Compute());
}
private void Compute()
{
// perform computation here
Dispatcher.Invoke(CoreDispatcherPriority.Normal, ShowText, this, resultString);
}
private void ShowText(object sender, InvokedHandlerArgs e)
{
this.TextBlock.Text = (string)e.Context;
}
Here is an easier way to do it I think!
First capture your UI SyncronizationContext with the following:
var UISyncContext = TaskScheduler.FromCurrentSynchronizationContext();
Run your server call operation or any other background thread operation you need:
Task serverTask= Task.Run(()=> { /* DoWorkHere(); */} );
Then do your UI operation on the UISyncContext you captured in first step:
Task uiTask= serverTask.ContinueWith((t)=>{TextBlockName.Text="your value"; }, UISyncContext);
IMO I think "ThreadPool" is the recommended route.
https://msdn.microsoft.com/en-us/library/windows/apps/xaml/hh465290.aspx
public static Task InvokeBackground(Func<Task> action)
{
var tcs = new TaskCompletionSource<bool>();
var unused = ThreadPool.RunAsync(async (obj) =>
{
await action();
tcs.TrySetResult(true);
});
return tcs.Task;
}

WPF / XAML: How do I execute threaded processes and prevent the main UI from being busy / freezing?

I have a XAML application that serves as the UI for an automation. The entire automation can take anywhere from 20-30 hours to fully execute so I created a Task class object that essentially wraps Thread methods (Start/Stop/Reset).
However, when I run the automation method under the Task object, the XAML UI is busy and I cannot interact with the other controls, including the Pause button which toggles the Thread.Set() flag.
There is another post
Prevent UI from freezing without additional threads
where someone recommended the BackgroundWorker class this MSDN article mentions it is a bad idea to use this when if it manipulates objects in the UI, which mine does for purposes of displaying status counts:
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
Any idea around this?
private void OnButtonStartAutomationClick(object sender, RoutedEventArgs e)
{
btnPauseAutomation.IsEnabled = true;
Automation.Task AutomationThread = new Automation.Task(RunFullAutomation);
}
private void RunFullAutomation()
{
// do stuff that can take 20+ hours
// threaded so I can utilize a pause button (block)
}
class Task
{
private ManualResetEvent _shutdownFlag = new ManualResetEvent(false);
private ManualResetEvent _pauseFlag = new ManualResetEvent(true);
private Thread _thread;
private readonly Action _action;
public Task(Action action)
{
_action = action;
}
public void Start()
{
ThreadStart ts = new ThreadStart(DoDelegatedMethod);
_thread = new Thread(ts);
_thread.Start();
_thread.Priority = ThreadPriority.Lowest;
}
public void Resume()
{
_pauseFlag.Set();
}
public void Stop()
{
_shutdownFlag.Set();
_pauseFlag.Set();
_thread.Join();
}
private void DoDelegatedMethod()
{
do
{
_action();
}
while (!_shutdownFlag.WaitOne(0));
}
}
where someone recommended the BackgroundWorker class this MSDN article mentions it is a bad idea to use this when if it manipulates objects in the UI, which mine does for purposes of displaying status counts
BackgroundWorker is actually ideal for this, as it was designed for this type of scenario. The warning is that you shouldn't change UI elements inside of DoWork, but rather via ReportProgress and the ProgressChanged event.
The reason the warning exists is "DoWork" is executed on a background thread. If you set a UI element value from there, you'll get a cross threading exception. However, ReportProgress/ProgressChanged automatically marshals the call back into the proper SynchronizationContext for you.
Take a look at the Dispatcher object in WPF. You can, and should in your scenario, run the long running tasks on a background thread and the BackgroundWorker is a good way to do it. When you need to update the UI you need to verify access to the UI thread and if you don't have it use the dispatcher to invoke an update method on the UI thread.
There are two possible causes here: first, that the blocking task is blocking the UI thread rather than running on a background thread, and second, that the background thread is starving the UI thread so that it never gets the chance to respond to input. You need to find out which of these is the case. A crude way to do this is, in your Click handler, Debug.WriteLine the current thread ID (Thread.CurrentThread.ManagedThreadId), and do the same in the RunFullAutomation callback.
If these print the same number, then you have the first problem. Reed and TheZenker have provided solutions to this.
If these print different numbers, then you are already on a worker thread, and you have the second problem. (BackgroundWorker may get you to the worker thread more elegantly, and will help with updating the UI, but it won't stop starvation.) In this case the simplest fix is probably to set _thread.Priority = ThreadPriority.BelowNormal; before starting the worker thread.
By the way, your code never appears to actually call AutomationThread.Start, which means the RunFullAutomation callback isn't even executed. Is this just a typo?
I'd advise against rolling out your own Task class given that .NET 4 has full support for running tasks asynchronously in the background using the Task Parallel Library
That said,you can do what Reed suggests and use a BackgroundWorker which is ideal or if you prefer more control over the nature of how the task si executing, you could use the Task class from System.Threading.Tasks and implement something like so:
public partial class MainWindow : Window
{
CancellationTokenSource source = new CancellationTokenSource();
SynchronizationContext context = SynchronizationContext.Current;
Task task;
public MainWindow()
{
InitializeComponent();
}
private void DoWork()
{
for (int i = 0; i <= 100; i++)
{
Thread.Sleep(500); //simulate long running task
if (source.IsCancellationRequested)
{
context.Send((_) => labelPrg.Content = "Cancelled!!!", null);
break;
}
context.Send((_) => labelPrg.Content = prg.Value = prg.Value + 1, null);
}
}
private void Start_Click(object sender, RoutedEventArgs e)
{
task = Task.Factory.StartNew(DoWork, source.Token);
}
private void Cancel_Click(object sender, RoutedEventArgs e)
{
source.Cancel();
}
}
In DoWork() you use the WPF SynchronizationContext and post messages to update the UI wiget you need.
The example has a progress bar and a label control that is updated on each iteration of the for loop.Cancellation is supported using CancellationTokenSource which is checked in each iteration.
Hope this helps.

Categories