can't get ContinueWhenAll to execute in UI thread - c#

The following is operating with a C# winforms application in .net 4.0
I have a list of tasks that execute in parallel. Once their execution is complete, there's a block of code I want to execute (relates to post-process validation). If any of the tasks fail, I want the exception to flow up the call stack to the UI level (I have a global exception handler that needs to be invoked).
I understand that ContinueWhenAll is not a blocking method. I also know that ContinueWhenAll is kicking off a new task. But I can't seem to make this task run in the same thread as the UI.
The exception is visible when running as Debug. But without debugging, the Continue'd task fails in its own thread and the exception goes unhandled and is lost.
I think my use of TaskContinuationOptions.ExecuteSynchronously is the cause (MSDN "the continuation will be run on the same thread that causes the antecedent task to transition into its final state"). Is there anyway I can force execution on the UI thread? Or am I using the wrong tool for the job?
//Being called in the UI thread
var tasks = new List<Task>();
foreach (var item in workList)
{
tasks.Add(item.DoWorkAsync);
}
var context = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.ContinueWhenAll(tasks.ToArray(), LoadComplete, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, context);
...
private void LoadComplete(Task[] tasks)
{
var errors = (from t in tasks where t.Exception != null select t.Exception);
if (errors.Count() > 0)
throw new AggregateException(errors.ToArray());
}

I just knocked together a very quick app to see what happens in .net 4 and 4.5 and after 10 tests all the continuation code ran on the UI thread
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
var tasks = new List<Task>();
Console.WriteLine("Main Thread" + System.Threading.Thread.CurrentThread.ManagedThreadId);
tasks.Add(new Task(() => Console.WriteLine("Task 1:" + System.Threading.Thread.CurrentThread.ManagedThreadId)));
tasks.Add(new Task(() => Console.WriteLine("Task 1:" + System.Threading.Thread.CurrentThread.ManagedThreadId)));
var context = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.ContinueWhenAll(tasks.ToArray(), LoadComplete, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, context);
tasks.ForEach(task => task.Start());
Console.ReadLine();
}
private void LoadComplete(Task[] tasks)
{
Console.WriteLine("Completion Task" + System.Threading.Thread.CurrentThread.ManagedThreadId);
}
}

So I did some digging and the way in which I'm using ContinueWhenAll is correct for the scenario I have (the tasks are IO related).
.net 4.5 has all kinds of good tools such as async / await but we need to keep this project in 4.0 for now. So the simplest way to ensure that LoadComplete runs in the UI thread is BeingInvoke
private void LoadComplete(Task[] tasks)
{
//Invoke on UI thread
if (this.InvokeRequired)
this.BeginInvoke((MethodInvoker)delegate
{
var errors = (from t in tasks where t.Exception != null select t.Exception);
if (errors.Count() > 0)
throw new AggregateException(errors.ToArray());
});
}

Related

How can I run a cancellable task to update UI component [duplicate]

I have a task that performing some heavy work.
I need to path it's result to LogContent
Task<Tuple<SupportedComunicationFormats, List<Tuple<TimeSpan, string>>>>.Factory
.StartNew(() => DoWork(dlg.FileName))
.ContinueWith(obj => LogContent = obj.Result);
This is the property:
public Tuple<SupportedComunicationFormats, List<Tuple<TimeSpan, string>>> LogContent
{
get { return _logContent; }
private set
{
_logContent = value;
if (_logContent != null)
{
string entry = string.Format("Recognized {0} log file",_logContent.Item1);
_traceEntryQueue.AddEntry(Origin.Internal, entry);
}
}
}
Problem is that _traceEntryQueue is data bound to UI, and of cause I will have exception on code like this.
So, my question is how to make it work correctly?
Here is a good article: Parallel Programming: Task Schedulers and Synchronization Context.
Take a look at Task.ContinueWith() method.
Example:
var context = TaskScheduler.FromCurrentSynchronizationContext();
var task = new Task<TResult>(() =>
{
TResult r = ...;
return r;
});
task.ContinueWith(t =>
{
// Update UI (and UI-related data) here: success status.
// t.Result contains the result.
},
CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, context);
task.ContinueWith(t =>
{
AggregateException aggregateException = t.Exception;
aggregateException.Handle(exception => true);
// Update UI (and UI-related data) here: failed status.
// t.Exception contains the occured exception.
},
CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, context);
task.Start();
Since .NET 4.5 supports async/await keywords (see also Task.Run vs Task.Factory.StartNew):
try
{
var result = await Task.Run(() => GetResult());
// Update UI: success.
// Use the result.
}
catch (Exception ex)
{
// Update UI: fail.
// Use the exception.
}
You need to run the ContinueWith -task on the UI thread. This can be accomplished using the TaskScheduler of the UI thread with the overloaded version of the ContinueWith -method, ie.
TaskScheduler scheduler = TaskScheduler.Current;
...ContinueWith(obj => LogContent = obj.Result), CancellationToken.None, TaskContinuationOptions.None, scheduler)
You can use the Dispatcher to invoke code on UI thread. Take a look at the article Working With The WPF Dispatcher
If you are using async/await, then here is some example code that shows how to schedule a task to run on the GUI thread. Place this code at the bottom of the stack of all of your async/await calls to avoid the WPF runtime throwing errors with code not executing on the GUI thread.
Works with WPF + MVVM, tested under VS 2013.
public async Task GridLayoutSetFromXmlAsync(string gridLayoutAsXml)
{
Task task = new Task(() => // Schedule some task here on the GUI thread );
task.RunSynchronously();
await task;
}

WhenAll() not working as expected

I need to make UI thread wait until a task array completes execution.The problem with below code is that - the tasks inturn invoke UI thread to write into textbox. How to fix this?
public partial class FormConsole : Form
{
public FormConsole()
{
InitializeComponent();
}
void txtSayHello_Click(object sender, EventArgs e)
{
Class1 objclss = new Class1();
objclss.formConsole = this;
Task[] taa = new Task[4];
taa[0] = new Task(() => objclss.DoSomeThigs("Hello world"));
taa[1] = new Task(() => objclss.DoSomeThigs("Hello world1"));
taa[2] = new Task(() => objclss.DoSomeThigs("Hello world2"));
taa[3] = new Task(() => objclss.DoSomeThigs("Hello world3"));
foreach(Task task in taa)
{
task.Start();
}
Task.WhenAll(taa);
this.txtConsole.AppendText("All threads complete");
}
delegate void doStuffDelegate(string value);
public void doStuff(string value)
{
if (System.Windows.Forms.Form.ActiveForm.InvokeRequired && IsHandleCreated)
{
BeginInvoke(new doStuffDelegate(doStuff), value);
}
else
txtConsole.AppendText(value);
}
}
public class Class1
{
public FormConsole formConsole;
public void DoSomeThigs(string sampleText)
{
formConsole.doStuff(sampleText);
}
}
o/p now : Console Redirection TestAll threads completeHello worldHello world1Hello world2Hello world3
o/p I want : Console Redirection TestHello worldHello world1Hello world2Hello world3All threads complete
What's the solution?
Task.WhenAll returns a task that completes when all tasks passed to it complete. You have to await this task, otherwise the method will continue executing.
async void txtSayHello_Click(object sender, EventArgs e)
{
...
await Task.WhenAll(taa);
...
}
There is a blocking version of this method - Task.WaitAll. It will block the current thread until all tasks are done, but it's not a good idea to block the UI thread.
Also, the preferred way to start a task on a thread pool thread is to use Task.Run.
Task.WhenAll returns a Task representing the completion of all these tasks in the enumerable. You need to await that task as the method doesn't block the thread.
Turn txtSayHello_Click into an async void (which should only be used for event handlers) method and await the task returned from Task.WhenAll:
async void txtSayHello_Click(object sender, EventArgs e)
{
// ...
await Task.WhenAll(taa);
// ...
}
Moreover, you should almost always avoid using the Task constructor. You should use Task.Factory.StartNew with TaskScheduler.FromSynchronizationContext if you need the task to run on the UI thread (that depends on what you actually do with FormConsole) or Task.Run if you don't. Meaning:
taa[0] = Task.Factory.StartNew(() => objclss.DoSomeThigs("Hello world"), CancellationToken.None, TaskCreationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
Or:
taa[0] = Task.Run(() => objclss.DoSomeThigs("Hello world"));
You're most likely confusing WhenAll() with WaitAll().
As already suggested you can use async with await or you can simply use :
A) Task.WhenAll(taa).Wait();
B) Task.WaitAll(taa);
But in your case this will block UI thread. So it's better to put rest of the code to Continuation Task and invoke UI operations with Control.Invoke() :
Task.WhenAll(taa).ContinueWith(t =>
{
this.Invoke(() => this.txtConsole.AppendText("All threads complete"));
});

How should I update from Task the UI Thread?

I have a task that performing some heavy work.
I need to path it's result to LogContent
Task<Tuple<SupportedComunicationFormats, List<Tuple<TimeSpan, string>>>>.Factory
.StartNew(() => DoWork(dlg.FileName))
.ContinueWith(obj => LogContent = obj.Result);
This is the property:
public Tuple<SupportedComunicationFormats, List<Tuple<TimeSpan, string>>> LogContent
{
get { return _logContent; }
private set
{
_logContent = value;
if (_logContent != null)
{
string entry = string.Format("Recognized {0} log file",_logContent.Item1);
_traceEntryQueue.AddEntry(Origin.Internal, entry);
}
}
}
Problem is that _traceEntryQueue is data bound to UI, and of cause I will have exception on code like this.
So, my question is how to make it work correctly?
Here is a good article: Parallel Programming: Task Schedulers and Synchronization Context.
Take a look at Task.ContinueWith() method.
Example:
var context = TaskScheduler.FromCurrentSynchronizationContext();
var task = new Task<TResult>(() =>
{
TResult r = ...;
return r;
});
task.ContinueWith(t =>
{
// Update UI (and UI-related data) here: success status.
// t.Result contains the result.
},
CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, context);
task.ContinueWith(t =>
{
AggregateException aggregateException = t.Exception;
aggregateException.Handle(exception => true);
// Update UI (and UI-related data) here: failed status.
// t.Exception contains the occured exception.
},
CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, context);
task.Start();
Since .NET 4.5 supports async/await keywords (see also Task.Run vs Task.Factory.StartNew):
try
{
var result = await Task.Run(() => GetResult());
// Update UI: success.
// Use the result.
}
catch (Exception ex)
{
// Update UI: fail.
// Use the exception.
}
You need to run the ContinueWith -task on the UI thread. This can be accomplished using the TaskScheduler of the UI thread with the overloaded version of the ContinueWith -method, ie.
TaskScheduler scheduler = TaskScheduler.Current;
...ContinueWith(obj => LogContent = obj.Result), CancellationToken.None, TaskContinuationOptions.None, scheduler)
You can use the Dispatcher to invoke code on UI thread. Take a look at the article Working With The WPF Dispatcher
If you are using async/await, then here is some example code that shows how to schedule a task to run on the GUI thread. Place this code at the bottom of the stack of all of your async/await calls to avoid the WPF runtime throwing errors with code not executing on the GUI thread.
Works with WPF + MVVM, tested under VS 2013.
public async Task GridLayoutSetFromXmlAsync(string gridLayoutAsXml)
{
Task task = new Task(() => // Schedule some task here on the GUI thread );
task.RunSynchronously();
await task;
}

Tasks, BackgroundWorkers or new Threads for database transactions in WPF MVVM?

I have a small MVVM application that communicates with a database. What (if any) is the standard way to perform database transactions in a background thread that updates the UI when complete? Should I use BackgroundWorkers, TPL, or implement my own Threads? Currently I have a static class with the following method for background work:
public static void RunAsync(Action backgroundWork, Action uiWork, Action<Exception> exceptionWork) {
var uiContext = TaskScheduler.FromCurrentSynchronizationContext();
// The time consuming work is run on a background thread.
var backgroundTask = new Task(() => backgroundWork());
// The UI work is run on the UI thread.
var uiTask = backgroundTask.ContinueWith(_ => { uiWork(); },
CancellationToken.None,
TaskContinuationOptions.OnlyOnRanToCompletion,
uiContext);
// Exceptions in the background task are handled on the UI thread.
var exceptionTask = backgroundTask.ContinueWith(t => { exceptionWork(t.Exception); },
CancellationToken.None,
TaskContinuationOptions.OnlyOnFaulted,
uiContext);
// Exceptions in the UI task are handled on on the UI thread.
var uiExceptionTask = uiTask.ContinueWith(t => { exceptionWork(t.Exception); },
CancellationToken.None,
TaskContinuationOptions.OnlyOnFaulted,
uiContext);
backgroundTask.Start();
}
You can use async/await, which will give you a more natural syntax:
public static async Task RunAsync(Action backgroundWork, Action uiWork, Action<Exception> exceptionWork)
{
try
{
// The time consuming work is run on a background thread.
await Task.Run(backgroundWork);
// The UI work is run on the UI thread.
uiWork();
}
catch (Exception ex)
{
// Exceptions in the background task and UI work are handled on the UI thread.
exceptionWork(ex);
}
}
Or better yet, just replace RunAsync with the code itself, so instead of
T[] values;
RunAsync(() => { values = GetDbValues(); }, () => UpdateUi(values), ex => UpdateUi(ex));
You can say:
try
{
var values = await Task.Run(() => GetDbValues());
UpdateUi(values);
}
catch (Exception ex)
{
UpdateUi(ex);
}
Well you can use any of these techniques. I would always run them on a separate thread though. The important thing is that the thread action is marshalled back onto the UI thread at the appropriate time. My preference is to use a task or async await if in .net 4.5

Why doesn't TaskScheduler.FromCurrentSynchronizationContext synchronize in Monotouch?

The thing i am interested in is why do we need to call InvokeOnMainThread while this would be the main intent and responsibility of TaskScheduler.FromCurrentSynchronizationContext()?.
I am using the TPL in Monotouch for an iPhone app to do some background tasks and update the UI via a reporter class. But it seems that TaskScheduler.FromCurrentSynchronizationContext() is not synchronizing to the UI thread as what you would expect. At this time I managed to get it working (but still feels wrong) by using InvokeOnMainThread as described by the Threading topic at Xamarin's site.
I also found a reported (similar) bug at BugZilla that seems to be resolved.. and another threading question about the preferred way of using background threads in MonoTouch.
Below is the code snippet to illustrate my question and to show the behaviour.
private CancellationTokenSource cancellationTokenSource;
private void StartBackgroundTask ()
{
this.cancellationTokenSource = new CancellationTokenSource ();
var cancellationToken = this.cancellationTokenSource.Token;
var progressReporter = new ProgressReporter ();
int n = 100;
var uiThreadId = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine ("Start in thread " + uiThreadId);
var task = Task.Factory.StartNew (() =>
{
for (int i = 0; i != n; ++i) {
Console.WriteLine ("Work in thread " + Thread.CurrentThread.ManagedThreadId);
Thread.Sleep (30);
progressReporter.ReportProgress (() =>
{
Console.WriteLine ("Reporting in thread {0} (should be {1})",
Thread.CurrentThread.ManagedThreadId,
uiThreadId);
this.progressBar.Progress = (float)(i + 1) / n;
this.progressLabel.Text = this.progressBar.Progress.ToString();
});
}
return 42; // Just a mock result
}, cancellationToken);
progressReporter.RegisterContinuation (task, () =>
{
Console.WriteLine ("Result in thread {0} (should be {1})",
Thread.CurrentThread.ManagedThreadId,
uiThreadId);
this.progressBar.Progress = (float)1;
this.progressLabel.Text = string.Empty;
Util.DisplayMessage ("Result","Background task result: " + task.Result);
});
}
And the reporter class has these methods
public void ReportProgress(Action action)
{
this.ReportProgressAsync(action).Wait();
}
public Task ReportProgressAsync(Action action)
{
return Task.Factory.StartNew(action, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
}
public Task RegisterContinuation(Task task, Action action)
{
return task.ContinueWith(() => action(), CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
}
public Task RegisterContinuation<TResult>(Task<TResult> task, Action action)
{
return task.ContinueWith(() => action(), CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
}
The results in the Application output window will be:
Start in thread 1
Work in thread 6
Reporting in thread 6 (should be 1)
Work in thread 6
Reporting in thread 6 (should be 1)
...
Result in thread 1 (should be 1)
As you can see 'Work in thread 6' is fine. Reporting is also on thread 6, this is wrong. The funny part is that the RegisterContinuation does its reporting in thread 1!!!
PROGRESS: I still haven't figured this one out.. Anyone?
I think the problem is that you're retrieving the task scheduler from within the ProgressReporter class by doing TaskScheduler.FromCurrentSynchronizationContext().
You should pass a task scheduler into the ProgressReporter and use that one instead:
public class ProgressReporter
{
private readonly TaskScheduler taskScheduler;
public ProgressReporter(TaskScheduler taskScheduler)
{
this.taskScheduler = taskScheduler;
}
public Task RegisterContinuation(Task task, Action action)
{
return task.ContinueWith(n => action(), CancellationToken.None,
TaskContinuationOptions.None, taskScheduler);
}
// Remaining members...
}
By passing the task scheduler taken from the UI thread into the progress reporter, you're sure that any reporting is done on the UI thread:
TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
ProgressReporter progressReporter = new ProgressReporter(uiScheduler);
What version of MonoTouch are you using and what is the output of: TaskScheduler.FromCurrentSynchronizationContext ().GetType ().ToString (). It should be a class of type UIKitSynchronizationContext if the context has been correctly registered. If this is a context of the correct type, could you do a quick test by directly calling the Post and Send methods on the context to see if they end up executing on the correct thread. You'll need to spin up a few threadpool threads to test that it works correctly, but it should be reasonably simple.

Categories