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

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;
}

Related

C# skips DispatcherOperation

I have a big time-consuming task and I try to implement asynchronous methods in order to prevent the application from blocking. My code looks like this:
CancellationTokenSource _cts;
async void asyncMethod()
{
// ..................
_cts = new CancellationTokenSource();
var progress = new Progress<double>(value => pbCalculationProgress.Value = value);
try
{
_cts.CancelAfter(25000);
int count = await awaitMethod(_cts.Token, progress);
}
catch (OperationCanceledException ex)
{
// .......
}
finally
{
_cts.Dispose();
}
// ..................
}
async Task<int> awaitMethod(CancellationToken ct, IProgress<double> progress)
{
var task = Task.Run(() =>
{
ct.ThrowIfCancellationRequested();
sqlParser();
progress.Report(1);
return 0;
});
return await task;
}
void sqlParser()
{
string info = form1TxtBox.Text;
// ................
}
Also, the program throws an exception, because sqlParser() updates UI thread, when it retrieves the text from the form. The solution is to introduce Dispatcher method, which allows UI update. I keep the body of awaitMethod the same and simply put sqlParser() inside of the Dispatcher:
DispatcherOperation op = Dispatcher.BeginInvoke((Action)(() =>
{
sqlParser();
}));
Here happens something interesting: asyncMethod() even doesn't dare to call awaitMethod! However, if I put a breakpoint inside of sqlParser() and run debugger, then everything goes very smoothly.
Please, can somebody explain what I miss in my code? What kind of patch should i use to make Dispatcher work correctly? Or: how can I run my program without Dispatcher and without throwing UI-update exception?
The solution is to introduce Dispatcher method, which allows UI update.
That's never a good solution. Having background threads reaching directly into your UI is encouraging spaghetti code.
how can I run my program without Dispatcher and without throwing UI-update exception?
Think of your background thread code as its own separate component, completely separate from the UI. If your background code needs data from the UI, then have your UI code read it before the background code starts, and pass that data into the background code.
async void asyncMethod()
{
...
try
{
var data = myUiComponent.Text;
_cts.CancelAfter(25000);
int count = await awaitMethod(data, _cts.Token, progress);
}
...
}
async Task<int> awaitMethod(string data, CancellationToken ct, IProgress<double> progress)
{
var task = Task.Run(() =>
{
ct.ThrowIfCancellationRequested();
sqlParser(data);
progress.Report(1);
return 0;
});
return await task;
}

Task WhenAll usage

I have a bunch of tasks defined as:
Task t1 = new Task( () => { /* Do Something */ } );
Task t2 = new Task( () => { /* Do Something */ } );
Task t3 = new Task( () => { /* Do Something */ } );
Task t4 = new Task( () => { /* Do Something */ } );
List<Task> allTasks = new List<Task>();
allTasks.Add(t1);
allTasks.Add(t2); etc.
And then finally:
Task.WhenAll(allTasks).ContinueWith((t) =>
{
MyBlockingCollection.CompleteAdding();
});
foreach (Task t in allTasks)
{
t.Start();
}
My questions regarding the above code:
Is this the correct way to make use of Tasks?
Does Task.WhenAll() start the tasks by itself or do we have to explicitly start them. If so, do we FIRST start and then do Task.WhenALL()?
And I need to do exception handling for these tasks as well, Could you please suggest the proper way to handle exceptions within tasks, Ideally I want the task to write out some diagnostics information to a text document if an exception happens.
I'm kind of new to the Tasks world, thanks for your help!
Does Task.WhenAll() start the tasks by itself or do we have to
explicitly start them. If so, do we FIRST start and then do
Task.WhenAll()?
You need to start every task individually first, after wait for them.
And I need to do exception handling for these tasks as well,..
Every task is independent execution unit, so exception handling happens inside its scope. That means that what you can do is to return an exception from the task as a result. Main thread will read a result and behave appropriately.
These Task instances will not run if you simply call the constructor. As the Tasks have not been started, WhenAll will never return and you will deadlock.
Use System.Threading.Task.Run instead.
Task t1 = Task.Run(() => { /* Do Something */ });
Task t2 = Task.Run(() => { /* Do Something */ });
...
Remove the loop in which you start the tasks.
Task.Run Method (Action) .NET Framework 4.5
Queues the specified work to run on the ThreadPool and returns a task
handle for that work.
From MSDN
Further reading here: Task Parallelism (Task Parallel Library)
Regarding exception handling, you can use the Task parameter to access all results, even exceptions, from within the continuation:
Task.WhenAll(allTasks).ContinueWith((t) =>
{
if(t.RanToCompletion)
{
MyBlockingCollection.CompleteAdding();
}
else
{
Console.WriteLine(t.Exception);
}
});
More on that here: Exception Handling (Task Parallel Library)

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