Why doesn't TaskScheduler.FromCurrentSynchronizationContext synchronize in Monotouch? - c#

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.

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

Method that executes a continuation of a Task in the main thread

I have to create a method, that similar to ContinueWith(), but will execute continuation in main thread, after main Task.
How can I do that?
I could endlessly checking the state of Task in my method, and when it finishes start continuation, but I think it couldn`t work in such way:
Task<DayOfWeek> taskA = new Task<DayOfWeek>(() => DateTime.Today.DayOfWeek);
Task<string> continuation = taskA.OurMethod((antecedent) =>
{
return String.Format("Today is {0}.", antecedent.Result);
});
// Because we endlessly checking state of main Task
// Code below will never execute
taskA.Start();
So what I could do here?
Try passing around the "main" thread's Dispatcher. Example:
Task.Factory.StartNew(()=>
{
// blah
}
.ContinueWith(task=>
{
Application.Current.Dispatcher.BeginInvoke(new Action(()=>
{
// yay, on the UI thread...
}
}
Assuming that the "main" thread is UI thread. If it's not, then grab that thread's dispatcher after you make it. Use that dispatcher instead of Application.Current's (i.e. CurrentDispatcher).
You can create an ExtensionMethod for a process like this. Here is an example implementation
static class ExtensionMethods
{
public static Task ContinueOnUI(this Task task, Action continuation)
{
return task.ContinueWith((arg) =>
{
Dispatcher.CurrentDispatcher.Invoke(continuation);
});
}
}
Consume it like this.
Task run = new Task(() =>
{
Debug.WriteLine("Testing");
});
run.ContinueOnUI(() =>
{
Notify += "\nExecuted On UI"; // Notify is bound on a UI control
});
run.Start();

Run "async" method on a background thread

I'm trying to run an "async" method from an ordinary method:
public string Prop
{
get { return _prop; }
set
{
_prop = value;
RaisePropertyChanged();
}
}
private async Task<string> GetSomething()
{
return await new Task<string>( () => {
Thread.Sleep(2000);
return "hello world";
});
}
public void Activate()
{
GetSomething.ContinueWith(task => Prop = task.Result).Start();
// ^ exception here
}
The exception thrown is:
Start may not be called on a continuation task.
What does that mean, anyway? How can I simply run my async method on a background thread, dispatch the result back to the UI thread?
Edit
Also tried Task.Wait, but the waiting never ends:
public void Activate()
{
Task.Factory.StartNew<string>( () => {
var task = GetSomething();
task.Wait();
// ^ stuck here
return task.Result;
}).ContinueWith(task => {
Prop = task.Result;
}, TaskScheduler.FromCurrentSynchronizationContext());
GetSomething.ContinueWith(task => Prop = task.Result).Start();
}
To fix your example specifically:
public void Activate()
{
Task.Factory.StartNew(() =>
{
//executes in thread pool.
return GetSomething(); // returns a Task.
}) // returns a Task<Task>.
.Unwrap() // "unwraps" the outer task, returning a proxy
// for the inner one returned by GetSomething().
.ContinueWith(task =>
{
// executes in UI thread.
Prop = task.Result;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
This will work, but it's old-school.
The modern way to run something on a background thread and dispatch back to UI thread is to use Task.Run(), async, and await:
async void Activate()
{
Prop = await Task.Run(() => GetSomething());
}
Task.Run will start something in a thread pool thread. When you await something, it automatically comes back in on the execution context which started it. In this case, your UI thread.
You should generally never need to call Start(). Prefer async methods, Task.Run, and Task.Factory.StartNew -- all of which start the tasks automatically. Continuations created with await or ContinueWith are also started automatically when their parent completes.
WARNING about using FromCurrentSynchronizationContext:
Ok, Cory knows how to make me rewrite answer:).
So the main culprit is actually the FromCurrentSynchronizationContext!
Any time StartNew or ContinueWith runs on this kind scheduler, it runs on the UI Thread. One may think:
OK, let's start subsequent operations on UI, change some controls, spawn some operations. But from now TaskScheduler.Current is not null and if any control has some events, that spawn some StartNew expecting to be running on ThreadPool, then from there it goes wrong. UI aps are usually complex, unease to maintain certainty, that nothing will call another StartNew operation, simple example here:
public partial class Form1 : Form
{
public static int Counter;
public static int Cnt => Interlocked.Increment(ref Counter);
private readonly TextBox _txt = new TextBox();
public static void WriteTrace(string from) => Trace.WriteLine($"{Cnt}:{from}:{Thread.CurrentThread.Name ?? "ThreadPool"}");
public Form1()
{
InitializeComponent();
Thread.CurrentThread.Name = "ThreadUI!";
//this seems to be so nice :)
_txt.TextChanged += (sender, args) => { TestB(); };
WriteTrace("Form1"); TestA(); WriteTrace("Form1");
}
private void TestA()
{
WriteTrace("TestA.Begin");
Task.Factory.StartNew(() => WriteTrace("TestA.StartNew"))
.ContinueWith(t =>
{
WriteTrace("TestA.ContinuWith");
_txt.Text = #"TestA has completed!";
}, TaskScheduler.FromCurrentSynchronizationContext());
WriteTrace("TestA.End");
}
private void TestB()
{
WriteTrace("TestB.Begin");
Task.Factory.StartNew(() => WriteTrace("TestB.StartNew - expected ThreadPool"))
.ContinueWith(t => WriteTrace("TestB.ContinueWith1 should be ThreadPool"))
.ContinueWith(t => WriteTrace("TestB.ContinueWith2"));
WriteTrace("TestB.End");
}
}
Form1:ThreadUI! - OK
TestA.Begin:ThreadUI! - OK
TestA.End:ThreadUI! - OK
Form1:ThreadUI! - OK
TestA.StartNew:ThreadPool - OK
TestA.ContinuWith:ThreadUI! - OK
TestB.Begin:ThreadUI! - OK
TestB.End:ThreadUI! - OK
TestB.StartNew - expected ThreadPool:ThreadUI! - COULD BE UNEXPECTED!
TestB.ContinueWith1 should be ThreadPool:ThreadUI! - COULD BE UNEXPECTED!
TestB.ContinueWith2:ThreadUI! - OK
Please notice, that tasks returned by:
async method,
Task.Fatory.StartNew,
Task.Run,
can not be started! They are already hot tasks...

can't get ContinueWhenAll to execute in UI thread

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

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

Categories