ASP.NET 4.6 async controller method loses HttpContext.Current after await - c#

I have an ASP.NET app targeting .NET 4.6 and I'm going crazy trying to figure out why HttpContext.Current becomes null after the first await inside my async MVC controller action.
I've checked and triple-checked that my project is targeting v4.6 and that the web.config's targetFramework attribute is 4.6 as well.
SynchronizationContext.Current is assigned both before and after the await and it's the right one, i.e. AspNetSynchronizationContext, not the legacy one.
FWIW, the await in question does switch threads on continuation, which is probably due to the fact that it invokes external I/O-bound code (an async database call) but that shouldn't be a problem, AFAIU.
And yet, it is! The fact that HttpContext.Current becomes null causes a number of problems for my code down the line and it doesn't make any sense to me.
I've checked the usual recommendations and I'm positive I'm doing everything I should be. I also have absolutely no ConfigureAwait's in my code!
What I DO have, is a couple of async event handlers on my HttpApplication instance:
public MvcApplication()
{
var helper = new EventHandlerTaskAsyncHelper(Application_PreRequestHandlerExecuteAsync);
AddOnPreRequestHandlerExecuteAsync(helper.BeginEventHandler, helper.EndEventHandler);
helper = new EventHandlerTaskAsyncHelper(Application_PostRequestHandlerExecuteAsync);
AddOnPostRequestHandlerExecuteAsync(helper.BeginEventHandler, helper.EndEventHandler);
}
I need these two because of custom authorization & cleanup logic, which requires async. AFAIU, this is supported and shouldn't be a problem.
What else could possibly be the reason for this puzzling behavior that I'm seeing?
UPDATE: Additional observation.
The SynchronizationContext reference stays the same after await vs. before await. But its internals change in between as can be seen in screenshots below!
BEFORE AWAIT:
AFTER AWAIT:
I'm not sure how (or even if) this might be relevant to my problem at this point. Hopefully someone else can see it!

I decided to define a watch on HttpContext.Current and started stepping "into" the await to see where exactly it changes. To no surprise, the thread was switched multiple times as I went on, which made sense to me because there were multiple true async calls on the way. They all preserved the HttpContext.Current instance as they are supposed to.
And then I hit the offending line...
var observer = new EventObserver();
using (EventMonitor.Instance.Observe(observer, ...))
{
await plan.ExecuteAsync(...);
}
var events = await observer.Task; // Doh!
The short explanation is that plan.ExecuteAsync performs a number of steps which are reported to a specialized event log in a non-blocking manner via a dedicated thread. This being business software, the pattern of reporting events is quite extensively used throughout the code. Most of the time, these events are of no direct concern to the caller. But one or two places are special in that the caller would like to know which events have occurred as a result of executing a certain code. That's when an EventObserver instance is used, as seen above.
The await observer.Task is necessary in order to wait for all relevant events to be processed and observed. The Task in question comes from a TaskCompletionSource instance, owned by the observer. Once all events have trickled in, the source's SetResult is called from a thread that processed the events. My original implementation of this detail was - very naively - as follows:
public class EventObserver : IObserver<T>
{
private readonly ObservedEvents _events = new ObservedEvents();
private readonly TaskCompletionSource<T> _source;
private readonly SynchronizationContext _capturedContext;
public EventObserver()
{
_source = new TaskCompletionSource<T>();
// Capture the current synchronization context.
_capturedContext = SynchronizationContext.Current;
}
void OnCompleted()
{
// Apply the captured synchronization context.
SynchronizationContext.SetSynchronizationContext(_capturedContext);
_source.SetResult(...);
}
}
I can now see that calling SetSynchronizationContext before SetResult isn't doing what I hoped it would be. The goal was to apply the original synchronization context to the continuation of the line await observer.Task.
The question now is: how do I do that properly? I'm guessing it will take an explicit ContinueWith call somewhere.
UPDATE
Here's what I did. I passed the TaskCreationOptions.RunContinuationsAsynchronously option the TaskCompletionSource ctor and modified the Task property on my EventObserver class to include explicitly synchronized continuation:
public Task<T> Task
{
get
{
return _source.Task.ContinueWith(t =>
{
if (_capturedContext != null)
{
SynchronizationContext.SetSynchronizationContext(_capturedContext);
}
return t.Result;
});
}
}
So now, when a code calls await observer.Task, the continuation will make sure the correct context is entered first. So far, it seems to be working correctly!

Related

What is the lifetime of SetSynchronizationContext?

I have a custom SynchronizationContext which abstracts away a custom work queue system. I'm trying to write a helper function which makes it easy for client code to write code for it.
My helper function currently looks like:
void async Task QueueTaskWithCustomContext(Func<Task> task) {
var oldContext = SynchronizationContext.Current;
try {
SynchronizationContext.SetCurrent(new CustomSynchronizationContext());
// Yielding prevents any inline synchronous work from being done outside the work queue.
await Task.Yield();
await task();
} finally {
SynchronizationContext.SetCurrent(oldContext);
}
}
And I expect to be used like:
await QueueTaskWithCustomContext(async() => {
await Something();
await SomethingElse();
});
I don't know if this is kosher but it seems to work for the test cases I've thrown at it. I'm a little nervous about how I'm setting the Synchronization context and how it might interact with asynchronous code, though. Specifically, are there any situations where the custom synchronization context could "leak" out of the helper function and be set for other tasks? I'm thinking if the helper function isn't awaited immediately. I couldn't make it happen after some quick experimentation but I'm still nervous.
Alternatively, is the way I've set up the try/finally block guaranteed to set the synchronization context even after the first await? Again, I couldn't make it happen from some quick experimentation but I'm still nervous about it.
I suppose I don't really understand the lifetime of setting the SynchronizationContext. Is it set forever, or until unset, or just for the current function or...?
Specifically, are there any situations where the custom synchronization context could "leak" out of the helper function and be set for other tasks?
Absolutely:
QueueTaskWithCustomContext(async() => await Task.Delay(1000));
await Task.Delay(10);
The smaller delay outside will run on your custom scheduler, not the default scheduler.
I wholeheartedly recommend against this kind of degenerate code, you're inviting nothing but trouble by surprising your users with something as basic as the task scheduler suddenly and unexpectedly changing.
Edit: Since I didn't explicitly answer your main question, the life time of the "current" task scheduler is the lifetime of the thread. It's stored in TLS so it will exist as long as your thread exists.

How to make HttpContext available in Task called synchronously?

In my ASP.NET MVC application, I need to implement/override some intrinsic entities like IModelBinder.BindModel(), IActionFilter.OnActionExecuting(), etc. Those methods are calling an async method, but obviously I cannot turn them into async Task<> to be able to use the await keyword.
So I wrote the following "adapter":
public static T GetResult<T>(Func<Task<T>> func)
{
var httpContext = HttpContext.Current;
var proxyTask = Task.Run(() =>
{
HttpContext.Current = httpContext;
return func();
});
return proxyTask.Result;
}
which lets me call my async GetData()in a synchronous manner:
public static async Task<Data> GetData()
{
if (isCached)
return GetCachedData();
var data = await GetOriginalData();
SetCachedData(data);
return data;
}
...
var data = GetResult(() => GetData());
Okay, it works, so I would call it a day, however as you can see, most of the time, GetData() runs synchronously, therefore spawning a new thread unconditionally is not good performance-wise. That was bugging me, so I have ended up with a different solution:
public static T GetResult<T>(Func<Task<T>> func)
{
var syncContext = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(null);
var task = func();
SynchronizationContext.SetSynchronizationContext(syncContext);
return task.Result;
}
It also works, however the issue is that since SynchronizationContext is no longer flowing in, there is no HttpContext available in the called method.
I could probably work that around by putting HttpContext.Current into logical CallContext but am still wondering if there is a better way to tackle the issue? Something like customizing SynchronizationContext.Current.CreateCopy().
spawning a new thread unconditionally
Actually, just borrowing a thread briefly from the thread pool. Not nearly as bad as starting a new thread, but still not the best performance-wise.
It also works, however the issue is that since SynchronizationContext is no longer flowing in, there is no HttpContext available in the called method.
Are you sure? I would expect HttpContext.Current to be set at the beginning of GetData for both ways to call it. I would also expect HttpContext.Current to be null after the await for both ways to call it. If it's not null after await in your testing, it's probably because the continuation just happened to end up on the same thread.
This is one of the main problems with trying to use HttpContext.Current outside of the request context (SynchronizationContext). When you place it on a bare thread (Task.Run), it will just stay there until that thread enters another request context.
I could probably work that around by putting HttpContext.Current into logical CallContext
I would recommend pulling out whatever data you need from Current while on the original request context, and then passing that data in explicitly to the methods that need it.
Regarding the original problem (avoiding the extra thread in the synchronous case), I'd recommend just adding a synchronous TryGetData method.

Can ConfigureAwait(false) in a library lose the synchronization context for the calling application?

I've read the advice many times from people smarter than me, and it has few caveats: Always use ConfigureAwait(false) inside library code. So I'm fairly certain I know the the answer, but I want to be 100%. The scenario is I have a library that thinly wraps some other asynchronous library.
Library code:
public async Task DoThingAsyc() {
// do some setup
return await otherLib.DoThingAsync().ConfigureAwait(false);
}
Application code:
// need to preserve my synchronization context
await myLib.DoThingAync();
// do I have my context here or did my lib lose it?
No.
The capturing of the SynchronizationContext happens on await. ConfigureAwait configures the specific await.
If the application calls a library's async method and awaits it the SC is captured on the spot regardless of what happens inside the call.
Now, because the async method's synchronous part (which is the part before the first await) is executed before a task is returned to be awaited, you can mess around with the SynchronizationContext there, but ConfigureAwait doesn't do that.
In your specific example you seem to be returning the result of ConfigureAwait from the async method. That can't happen because ConfigureAwait returns the ConfiguredTaskAwaitable struct. If however we change the method return type:
public ConfiguredTaskAwaitable DoThingAsyc()
{
return otherLib.DoThingAsync().ConfigureAwait(false);
}
Then awaiting it will indeed affect the calling code's await behavior.
Example from http://blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx
... logically you can think of the following code:
await FooAsync();
RestOfMethod();
as being similar in nature to this:
var t = FooAsync();
var currentContext = SynchronizationContext.Current;
t.ContinueWith(delegate
{
if (currentContext == null)
RestOfMethod();
else
currentContext.Post(delegate { RestOfMethod(); }, null);
}, TaskScheduler.Current);
which means you should have your context back after the await myLib.DoThingAync(); call.
If used inconsistently in the logical chain of async calls, ConfigureAwait(false) may add redundant context switches (which usually means redundant thread switches). This may happen in the presence of synchronization context, when some async calls on the logical stack use ConfigureAwait(false) and some don't (more here).
You still should use ConfigureAwait(false) in your code, but you may want to peek into the 3rd party code you're calling and mitigate any inconsistency with something like this:
public async Task DoThingAsyc() {
// do some setup
await Task.Run(() => otherLib.DoThingAsync()).ConfigureAwait(false);
// do some other stuff
}
This would add one extra thread switch, but might potentially prevent many others.
Moreover, if you're creating a really thin wrapper like you showed, you may want to implement it like below, without async/await at all:
public Task DoThingAsyc() {
// do some setup
return otherLib.DoThingAsync();
}

Different behavior when using ContinueWith or Async-Await

When I use an async-await method (as the example below) in a HttpClient call, this code causes a deadlock. Replacing the async-await method with a t.ContinueWith, it works properly. Why?
public class MyFilter: ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext filterContext) {
var user = _authService.GetUserAsync(username).Result;
}
}
public class AuthService: IAuthService {
public async Task<User> GetUserAsync (string username) {
var jsonUsr = await _httpClientWrp.GetStringAsync(url).ConfigureAwait(false);
return await JsonConvert.DeserializeObjectAsync<User>(jsonUsr);
}
}
This works:
public class HttpClientWrapper : IHttpClient {
public Task<string> GetStringAsync(string url) {
return _client.GetStringAsync(url).ContinueWith(t => {
_log.InfoFormat("Response: {0}", url);
return t.Result;
});
}
This code will deadlock:
public class HttpClientWrapper : IHttpClient {
public async Task<string> GetStringAsync(string url) {
string result = await _client.GetStringAsync(url);
_log.InfoFormat("Response: {0}", url);
return result;
}
}
I describe this deadlock behavior on my blog and in a recent MSDN article.
await will by default schedule its continuation to run inside the current SynchronizationContext, or (if there is no SynchronizationContext) the current TaskScheduler. (Which in this case is the ASP.NET request SynchronizationContext).
The ASP.NET SynchronizationContext represents the request context, and ASP.NET only allows one thread in that context at a time.
So, when the HTTP request completes, it attempts to enter the SynchronizationContext to run InfoFormat. However, there is already a thread in the SynchronizationContext - the one blocked on Result (waiting for the async method to complete).
On the other hand, the default behavior for ContinueWith by default will schedule its continuation to the current TaskScheduler (which in this case is the thread pool TaskScheduler).
As others have noted, it's best to use await "all the way", i.e., don't block on async code. Unfortunately, that's not an option in this case since MVC does not support asynchronous action filters (as a side note, please vote for this support here).
So, your options are to use ConfigureAwait(false) or to just use synchronous methods. In this case, I recommend synchronous methods. ConfigureAwait(false) only works if the Task it's applied to has not already completed, so I recommend that once you use ConfigureAwait(false), you should use it for every await in the method after that point (and in this case, in each method in the call stack). If ConfigureAwait(false) is being used for efficiency reasons, then that's fine (because it's technically optional). In this case, ConfigureAwait(false) would be necessary for correctness reasons, so IMO it creates a maintenance burden. Synchronous methods would be clearer.
An explanation on why your await deadlocks
Your first line:
var user = _authService.GetUserAsync(username).Result;
blocks that thread and the current context while it waits for the result of GetUserAsync.
When using await it attempts to run any remaining statements back on the original context after the task being waited on finishes, which causes deadlocks if the original context is blocked (which is is because of the .Result). It looks like you attempted to preempt this problem by using .ConfigureAwait(false) in GetUserAsync, however by the time that that await is in effect it's too late because another await is encountered first. The actual execution path looks like this:
_authService.GetUserAsync(username)
_httpClientWrp.GetStringAsync(url) // not actually awaiting yet (it needs a result before it can be awaited)
await _client.GetStringAsync(url) // here's the first await that takes effect
When _client.GetStringAsync finishes, the rest of the code can't continue on the original context because that context is blocked.
Why ContinueWith behaves differently
ContinueWith doesn't try to run the other block on the original context (unless you tell it to with an additional parameter) and thus does not suffer from this problem.
This is the difference in behavior that you noticed.
A solution with async
If you still want to use async instead of ContinueWith, you can add the .ConfigureAwait(false) to the first encountered async:
string result = await _client.GetStringAsync(url).ConfigureAwait(false);
which as you most likely already know, tells await not to try to run the remaining code on the original context.
Note for the future
Whenever possible, attempt to not use blocking methods when using async/await. See Preventing a deadlock when calling an async method without using await for avoiding this in the future.
Granted, my answer is only partial, but I'll go ahead with it anyway.
Your Task.ContinueWith(...) call does not specify the scheduler, therefore TaskScheduler.Current will be used - whatever that is at the time. Your await snippet, however, will run on the captured context when the awaited task completes, so the two bits of code may or may not produce similar behaviour - depending on the value of TaskScheduler.Current.
If, say, your first snippet is called from the UI code directly (in which case TaskScheduler.Current == TaskScheduler.Default, the continuation (logging code) will execute on the default TaskScheduler - that is, on the thread pool.
In the second snippet, however, the continuation (logging) will actually run on the UI thread regardless of whether you use ConfigureAwait(false) on the task returned by GetStringAsync, or not. ConfigureAwait(false) will only affect the execution of the code after the call to GetStringAsync is awaited.
Here's something else to illustrate this:
private async void Form1_Load(object sender, EventArgs e)
{
await this.Blah().ConfigureAwait(false);
// InvalidOperationException here.
this.Text = "Oh noes, I'm no longer on the UI thread.";
}
private async Task Blah()
{
await Task.Delay(1000);
this.Text = "Hi, I'm on the UI thread.";
}
The given code sets the Text within Blah() just fine, but it throws a cross-threading exception inside the continuation in the Load handler.
I found the other solutions posted here did not work for me on ASP .NET MVC 5, which still uses synchronous Action Filters. The posted solutions don't guarantee a new thread will be used, they just specify that the same thread does not HAVE to be used.
My solution is to use Task.Factory.StartNew() and specifying TaskCreationOptions.LongRunning in the method call. This ensures a new/different thread is always used, so you can be assured you will never get a deadlock.
So, using the OP example, the following is the solution that works for me:
public class MyFilter: ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext filterContext) {
// var user = _authService.GetUserAsync(username).Result;
// Guarantees a new/different thread will be used to make the enclosed action
// avoiding deadlocking the calling thread
var user = Task.Factory.StartNew(
() => _authService.GetUserAsync(username).Result,
TaskCreationOptions.LongRunning).Result;
}
}

Why is TaskScheduler.Current the default TaskScheduler?

The Task Parallel Library is great and I've used it a lot in the past months. However, there's something really bothering me: the fact that TaskScheduler.Current is the default task scheduler, not TaskScheduler.Default. This is absolutely not obvious at first glance in the documentation nor samples.
Current can lead to subtle bugs since its behavior is changing depending on whether you're inside another task. Which can't be determined easily.
Suppose I am writting a library of asynchronous methods, using the standard async pattern based on events to signal completion on the original synchronisation context, in the exact same way XxxAsync methods do in the .NET Framework (eg DownloadFileAsync). I decide to use the Task Parallel Library for implementation because it's really easy to implement this behavior with the following code:
public class MyLibrary
{
public event EventHandler SomeOperationCompleted;
private void OnSomeOperationCompleted()
{
SomeOperationCompleted?.Invoke(this, EventArgs.Empty);
}
public void DoSomeOperationAsync()
{
Task.Factory.StartNew(() =>
{
Thread.Sleep(1000); // simulate a long operation
}, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default)
.ContinueWith(t =>
{
OnSomeOperationCompleted(); // trigger the event
}, TaskScheduler.FromCurrentSynchronizationContext());
}
}
So far, everything works well. Now, let's make a call to this library on a button click in a WPF or WinForms application:
private void Button_OnClick(object sender, EventArgs args)
{
var myLibrary = new MyLibrary();
myLibrary.SomeOperationCompleted += (s, e) => DoSomethingElse();
myLibrary.DoSomeOperationAsync(); // call that triggers the event asynchronously
}
private void DoSomethingElse() // the event handler
{
//...
Task.Factory.StartNew(() => Thread.Sleep(5000)); // simulate a long operation
//...
}
Here, the person writing the library call chose to start a new Task when the operation completes. Nothing unusual. He or she follows examples found everywhere on the web and simply use Task.Factory.StartNew without specifying the TaskScheduler (and there is no easy overload to specify it at the second parameter). The DoSomethingElse method works fine when called alone, but as soon at it's invoked by the event, the UI freezes since TaskFactory.Current will reuse the synchronization context task scheduler from my library continuation.
Finding out this could take some time, especially if the second task call is buried down in some complex call stack. Of course, the fix here is simple once you know how everything works: always specify TaskScheduler.Default for any operation you're expecting to be running on the thread pool. However, maybe the second task is started by another external library, not knowing about this behavior and naively using StartNew without a specific scheduler. I'm expecting this case to be quite common.
After wrapping my head around it, I can't understand the choice of the team writing the TPL to use TaskScheduler.Current instead of TaskScheduler.Default as the default:
It's not obvious at all, Default is not the default! And the documentation is seriously lacking.
The real task scheduler used by Current depends of the call stack! It's hard to maintain invariants with this behavior.
It's cumbersome to specify the task scheduler with StartNew since you have to specify the task creation options and cancellation token first, leading to long, less readable lines. This can be alleviated by writing an extension method or creating a TaskFactory that uses Default.
Capturing the call stack has additional performance costs.
When I really want a task to be dependent on another parent running task, I prefer to specify it explicitly to ease code reading rather than rely on call stack magic.
I know this question may sound quite subjective, but I can't find a good objective argument as to why this behavior is as it. I'm sure I'm missing something here: that's why I'm turning to you.
I think the current behavior makes sense. If I create my own task scheduler, and start some task that starts other tasks, I probably want all the tasks to use the scheduler I created.
I agree that it's odd that sometimes starting a task from the UI thread uses the default scheduler and sometimes not. But I don't know how would I make this better if I was designing it.
Regarding your specific problems:
I think the easiest way to start a new task on a specified scheduler is new Task(lambda).Start(scheduler). This has the disadvantage that you have to specify type argument if the task returns something. TaskFactory.Create can infer the type for you.
You can use Dispatcher.Invoke() instead of using TaskScheduler.FromCurrentSynchronizationContext().
[EDIT]
The following only addresses the problem with the scheduler used by Task.Factory.StartNew.
However, Task.ContinueWith has a hardcoded TaskScheduler.Current.
[/EDIT]
First, there is an easy solution available - see the bottom of this post.
The reason behind this problem is simple: There is not only a default task scheduler (TaskScheduler.Default) but also a default task scheduler for a TaskFactory (TaskFactory.Scheduler).
This default scheduler can be specified in the constructor of the TaskFactory when it's created.
However, the TaskFactory behind Task.Factory is created as follows:
s_factory = new TaskFactory();
As you can see, no TaskScheduler is specified; null is used for the default constructor - better would be TaskScheduler.Default (the documentation states that "Current" is used which has the same consequences).
This again leads to the implementation of TaskFactory.DefaultScheduler (a private member):
private TaskScheduler DefaultScheduler
{
get
{
if (m_defaultScheduler == null) return TaskScheduler.Current;
else return m_defaultScheduler;
}
}
Here you should see be able to recognize the reason for this behaviour: As Task.Factory has no default task scheduler, the current one will be used.
So why don't we run into NullReferenceExceptions then, when no Task is currently executing (i.e. we have no current TaskScheduler)?
The reason is simple:
public static TaskScheduler Current
{
get
{
Task internalCurrent = Task.InternalCurrent;
if (internalCurrent != null)
{
return internalCurrent.ExecutingTaskScheduler;
}
return Default;
}
}
TaskScheduler.Current defaults to TaskScheduler.Default.
I would call this a very unfortunate implementation.
However, there is an easy fix available: We can simply set the default TaskScheduler of Task.Factory to TaskScheduler.Default
TaskFactory factory = Task.Factory;
factory.GetType().InvokeMember("m_defaultScheduler", BindingFlags.SetField | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly, null, factory, new object[] { TaskScheduler.Default });
I hope I could help with my response although it's quite late :-)
Instead of Task.Factory.StartNew()
consider using: Task.Run()
This will always execute on a thread pool thread. I just had the same problem described in the question and I think that is a good way of handling this.
See this blog entry:
http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx
It's not obvious at all, Default is not the default! And the documentation is seriously lacking.
Default is the default, but it's not always the Current.
As others have already answered, if you want a task to run on the thread pool, you need to explicitly set the Current scheduler by passing the Default scheduler into either the TaskFactory or the StartNew method.
Since your question involved a library though, I think the answer is that you should not do anything that will change the Current scheduler that's seen by code outside your library. That means that you should not use TaskScheduler.FromCurrentSynchronizationContext() when you raise the SomeOperationCompleted event. Instead, do something like this:
public void DoSomeOperationAsync() {
var context = SynchronizationContext.Current;
Task.Factory
.StartNew(() => Thread.Sleep(1000) /* simulate a long operation */)
.ContinueWith(t => {
context.Post(_ => OnSomeOperationCompleted(), null);
});
}
I don't even think you need to explicitly start your task on the Default scheduler - let the caller determine the Current scheduler if they want to.
I've just spent hours trying to debug a weird issue where my task was scheduled on the UI thread, even though I didn't specify it to. It turned out the problem was exactly what your sample code demonstrated: A task continuation was scheduled on the UI thread, and somewhere in that continuation, a new task was started which then got scheduled on the UI thread, because the currently executing task had a specific TaskScheduler set.
Luckily, it's all code I own, so I can fix it by making sure my code specify TaskScheduler.Default when starting new tasks, but if you aren't so lucky, my suggestion would be to use Dispatcher.BeginInvoke instead of using the UI scheduler.
So, instead of:
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
var task = Task.Factory.StartNew(() => Thread.Sleep(5000));
task.ContinueWith((t) => UpdateUI(), uiScheduler);
Try:
var uiDispatcher = Dispatcher.CurrentDispatcher;
var task = Task.Factory.StartNew(() => Thread.Sleep(5000));
task.ContinueWith((t) => uiDispatcher.BeginInvoke(new Action(() => UpdateUI())));
It's a bit less readable though.

Categories

Resources