Why is using Task.Run() still blocking my UI? - c#

I have a synchronous method I am calling with Task.Run() and my UI is being blocked and unresponsive. The method loads information from a database via COM Interop and I don't have any control over that.
public List<EdmAddInInfo2> GetInstalledAddins()
{
IEdmAddInMgr7 addinMgr = m_vault as IEdmAddInMgr7;
Array installedAddins = Array.CreateInstance(typeof(EdmAddInInfo2), 0);
addinMgr.GetInstalledAddIns(out installedAddins);
if (installedAddins?.Length > 0)
return installedAddins.OfType<EdmAddInInfo2>().ToList();
return null;
}
I am calling the method this way when my form is shown;
private async void LicensesForm_Shown(object sender, EventArgs e)
{
var m_addins = await GetInstalledAddins().ConfigureAwait(false);
toolStripStatusLabel2.Text = $"Loaded {m_addins.Count} addins.";
}
private async Task<List<EdmAddInInfo2>> GetInstalledAddins()
{
AddinManager addinMgr = new AddinManager(Vault);
var addins = await Task.Run(() => addinMgr.GetInstalledAddins()).ConfigureAwait(false);
return addins;
}
Usually I would use a BCW and be on my way, but I figured I would give Tasks a shot. Any ideas?

After the discussion in comments, I'm thinking that there is something deeper going on involving COM. One thing to be aware of when involving COM is that it uses the Dispatcher for receiving events, which has bitten me before. If the issue is related to COM one would probably need more information about what is going on and that digging might not be worthwhile. I wish I could be more helpful, but I think I'll have to default to advising the easy way out. Launch a thread to call GetInstalledAddins, assign the result to a local variable, and notify the UI of completion through the Dispatcher.
Also, from my original answer before editing to add the above,
var m_addins = await GetInstalledAddins().ConfigureAwait(false);
should be:
var m_addins = await GetInstalledAddins().ConfigureAwait(true);
This is because on the next line you assign to a UI element's Text property. This assignment must be done from the UI thread, which is active when you call GetInstalledAddins(), but because you then call ConfigureAwait(false) the execution continues after await on any thread the async manager (I forget what it's called) chooses.
One of the strengths of using async/await in UI code is that execution can (in normal situations) be resumed on the same thread the await call was made on. This way you can continue accessing UI objects after the await. But your call to ConfigureAwait(false) instructs the async/await engine that you don't care which thread the execution is resumed on (but in this case you really should care that the execution is resumed on the same thread).

The method loads information from a database via COM Interop and I don't have any control over that.
Well then, depending on the implementation of that method, it may not be possible to unblock the UI thread.
However, you could try this: if that type is allocating COM objects in its constructor, they may be getting tied to the UI thread. I would try creating the instance on the background thread:
private Task<List<EdmAddInInfo2>> GetInstalledAddins()
{
return Task.Run(() => new AddinManager(Vault).GetInstalledAddins());
}
Usually I would use a BCW and be on my way
BackgroundWorker would have the exact same problem.

Related

Task.Wait for async method passes during application startup while it causes deadlock in WPF button handler

The behavior of Task.Wait() is unexpectedly different depending on the "environment" where invoked.
Calling Task.Wait() during application startup with below async method TestAsync passes (doesn't cause a deadlock) while the same code blocks when called from within a WPF Button handler.
Steps to reproduce:
In Visual Studio, using the wizard, create a vanilla WPF .NET framework application (e.g. named WpfApp).
In the App.xaml.cs file of the app file paste below Main method and TestAsync method.
In the project properties set Startup object to WpfApp.App.
In the properties of App.xaml switch Build Action from ApplicationDefinition to Page.
public partial class App : Application
{
[STAThread]
public static int Main(string[] args)
{
Task<DateTime> task = App.TestAsync();
task.Wait();
App app = new App();
app.InitializeComponent();
return app.Run();
}
internal static async Task<DateTime> TestAsync()
{
DateTime completed = await Task.Run<DateTime>(() => {
System.Threading.Thread.Sleep(3000);
return DateTime.Now;
});
System.Diagnostics.Debug.WriteLine(completed);
return completed;
}
}
Observe that the application starts properly (after 3sec delay) and that the "completed" DateTime is written to debug output.
Next create a Button in MainWindow.xaml with Click handler Button_Click in MainWindow.xaml.cs
public partial class MainWindow : Window
{
...
private void Button_Click(object sender, RoutedEventArgs e)
{
Task<DateTime> task = App.TestAsync();
task.Wait();
}
}
Observe that after clicking the Button, the application is deadlocked.
Why can't it pass in both cases?
Is there a way to change invocation (e.g. using ConfigureAwait at the correct task or somehow setting SynchronizationContext or whatever) so that it behaves identical in both invocations, but still synchronously waits for completion?
Update on limitations of the solution.
The async method like TestAsync comes from a library that cannot be changed.
The invocation code of the TestAsync method is nested within a callstack that cannot be changed either, and the code outside the callstck makes use of the returned value of the async method.
Ultimately the solution code has to convert the async method to run synchronous by not changing the method nor the caller.
This works well within UT code (NUnit) and during application startup, but no more within a handler of WPF.
Why?
There are a couple of different ways that you can handle this situation, but ultimately the reason there is a deadlock in one situation and not the other is that when called in the Main method SynchronizationContext.Current is null, so there isn't a main UI context to capture and all async callbacks are handled on thread pool threads. When called from the button, there is a synchronization context which is captured automatically, so all async callbacks in that situation are handled on the main UI thread which is causing the deadlock. In general the only way you won't get that deadlock is by forcing the async code to not capture the synchronization context, or use async all the way up and don't synchronously wait from the main UI context.
you can ConfigureAwait(false) inside of your TestAsync method so that it doesn't capture the synchronization context and try to continue on the main UI thread (this is ultimately what is causing your deadlock because you are calling task.Wait() on the UI thread which is blocking the UI thread, and you have System.Diagnostics.Debug.WriteLine(completed); that is trying to be scheduled back onto the UI thread because await automatically captures the synchronization context)
DateTime completed = await Task.Run<DateTime>(() => {
System.Threading.Thread.Sleep(3000);
return DateTime.Now;
}).ConfigureAwait(false);
You can start the async task on a background thread so that there isn't a synchronization context to capture.
private void Button_Click(object sender, RoutedEventArgs e)
{
var task = Task.Run(() => App.TestAsync());
var dateTime = task.Result;
}
you can use async up the whole stack
private async void Button_Click(object sender, RoutedEventArgs e)
{
Task<DateTime> task = App.TestAsync();
var dateTime = await task;
}
Given how you are using it, if you don't have to wait until the task is done, you can just let it go and it will finish eventually, but you lose the context to handle any exceptions
private void Button_Click(object sender, RoutedEventArgs e)
{
//assigning to a variable indicates to the compiler that you
//know the application will continue on without checking if
//the task is finished. If you aren't using the variable, you
//can use the throw away special character _
_ = App.TestAsync();
}
These options are not in any particular order, and actually, best practice would probably be #3. async void is allowed specifically for cases like this where you want to handle a callback event asynchronously.
From what I understand, in .NET many of the front ends have a single UI thread, and therefore must be written async all the way through. Other threads are reserved and utilized for things like rendering.
For WPF, this is why use of the Dispatcher and how you queue up work items is important, as this is your way to interact with the one thread you have at your disposal. More reading on it here
Ditch the .Result as this will block, rewrite the method as async, and call it from within the Dispatch.Invoke() and it should run as intended
Why can't it pass in both cases?
The difference is the presence of a SynchronizationContext. All threads start out without a SynchronizationContext. UI applications have a special UI thread(s) and at some point they need to create a SynchronizationContext and install it on that thread(s). Exactly when this happens isn't documented (or consistent), but it has to be installed at the point the UI main loop starts.
In this case, WPF will install it (at the latest) within the call to Application.Run. All user invocations from the UI framework (e.g., event handlers) happen within this context.
The blocking code deadlocks with the context because this is the classic deadlock situation, which requires three components:
A context that only allows one thread at a time.
An asynchronous method that captures that context.
A method also running in that context that blocks waiting for that asynchronous method.
Before the WPF code installed the context, condition (1) wasn't met, and that's why it didn't deadlock.
Is there a way to change invocation (e.g. using ConfigureAwait at the correct task or somehow setting SynchronizationContext or whatever) so that it behaves identical in both invocations, but still synchronously waits for completion?
We-ell...
This is a rephrasing of "how do I block on asynchronous code", and there's no good answer for that. The best answer is to not block on asynchronous code at all; i.e., use async all the way. Especially since this is GUI code, I'd say for the sake of UX you really want to avoid blocking. Since you're on WPF, you may find a technique like asynchronous MVVM data binding useful.
That said, there are a few hacks you can use if you must. Using ConfigureAwait is one possible solution, but not one I recommend; you'd have to apply it to all awaits within the transitive closure of all methods being blocked on (Blocking Hack). Or you can shunt the work to the thread pool (Task.Run) and block on that (Thread Pool Hack). Or you can remove the SynchronizationContext - unless the code being blocked on manipulates UI elements or bound data. Or there are even more dangerous hacks that I really can't recommend at all (Nested Message Loop Hack).
But even after putting in all the work for a hack, you'll still end up blocking the UI. The hacks are hard precisely because they're not recommended. It's quite a bit of work to give your users a worse experience. The far, far better solution (for your users and future code maintainers) is to go async all the way.

In C# Winforms What is the most precise method of having an asynchronous method guarantee its own execution on the UI thread?

In doing my research here there seem to be about a million different ways to accomplish this, but the pros and cons of each are less unclear. One possibility that seems to accomplish what I'm after is:
private async Task SomeMostlyUIMethod(byte[] someParam = null)
{
if (InvokeRequired)
{
Task result = Task.Run(() => Invoke((Action)(async () => await SomeMostlyUIMethod(someParam))));
await result;
return;
}
else
{
// do some stuff
}
}
That seems to work, it feels like there should be a more clear solution for this. I'm not invoking on a per control modified basis because the method modifies a large number of different UI components, but also need to keep the method asynchronous since it has some expensive non-ui tasks it carries out as well.
to make work item will be executed on UI thread, you probably need the current synchronization context:
factory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext());
and use that factory to StartNew any task involved in GUI control updating.
You need to launch the async operation from an UI thread (main thread):
await Task.Run(DoYourStuffAsync);
This is equivalent to:
await Task.Run(DoYourStuffAsync).ConfigureAwait(true);
If the boolean argument of ConfigureAwait (bool continueOnCapturedContext) is true, the code that is below the await (also called continuation) will be executed in the calling thread (UI thread).
What I suggest is to do all your processing in an async way, create an outcome object, and then update the UI element in the continuation with the outcome object.
In this way you are using the UI thread only for UI stuff.

C# await tasks + infinite loop still freezing the UI

I am trying to get the proper 'structure' for monitoring the state of a game from external source(s) using (Tasks) async/await in order to run the tasks in an infinite loop, however the current way its written seems to just freeze up my UI.
What I have so far:
(in the "state machine" class)
// Start monitoring the game state for changes
public void Start()
{
tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
IsRunning = true;
task = Task.Factory.StartNew(async () =>
{
while (true)
{
await Task.Run(()=>CheckForStateChange());
await Task.Delay(1000); // Pause 1 second before checking state again
}
}, token, TaskCreationOptions.LongRunning, TaskScheduler.FromCurrentSynchronizationContext());
}
Without the above "Task.Delay" line the UI completely freezes up. With the "Task.Delay" line it doesn't freeze up, but if I try to drag the window it skips back to where I began dragging it.
My assumption with the current code is that the 'await Task.Run()' executes and upon completion the 'await Task.Delay()' executes and then on completion returns to the beginning of the while(true) infinite loop. (ie. not running in parallel).
The CheckForStateChange() signature is as follows:
private void CheckForStateChange()
{
// ... A bunch of code to determine and update the current state value of the object
}
Nothing special there, simple non-async method. I have read through lots of examples / questions here on StackOverflow and I used to have CheckForStateChange as returning a Task (with awaitable actions inside the method) and many other iterations of code (with the same results).
Finally I call the Start() method from the main win32 form (button) as follows:
private void btnStartSW_Click(object sender, EventArgs e)
{
// Start the subscription of the event handler
if(!state.IsRunning)
{
state.StateChange += new SummonersWar.StateChangeHandler(OnGameStateChange);
state.Start();
}
}
I think the above code is the simplest form I have written the code structure in so far, but apparently its still not written 'properly'. Any help would be appreciated.
UPDATE:
The publisher side (state machine class):
// ------ Publisher of the event ---
public delegate void StateChangeHandler(string stateText);
public event StateChangeHandler StateChange;
protected void OnStateChange() // TODO pass text?
{
if (StateChange != null)
StateChange(StateText());
}
Where the StateText() method is just a temporary way of retrieving a 'text' representation of the current state (and is really a placeholder at this point until I organize it into a tidier struct)
IsRunning is purely a public bool.
And the handler in the UI thread:
private void OnGameStateChange(string stateText)
{
// Game State Changed (update the status bar)
labelGameState.Text = "State: " + stateText;
}
Why the UI freezes
In terms of the main question: you're already calling your CheckForStateChange via Task.Run, so there is no way that your CheckForStateChange will freeze the UI unless it includes calls which are marshalled back to the UI thread (i.e. Control.Invoke or SynchronizationContext.Post/Send used explicitly, or implicitly via a Task started on the UI TaskScheduler).
The best place to start looking is your StateChange handlers (i.e. StateChangeHandler). Also have a look at where the StateChange event is raised. You'll find thread marshalling code at one of these sites.
Other issues
You're passing the TaskScheduler pointing to the UI SynchronizationContext to the outer task. You're also passing in TaskCreationOptions.LongRunning. In simple terms you're telling the task factory to "start a task on a dedicated thread, and on the current thread". These two are mutually exclusive requirements and you can pretty safely drop them both.
If, as a result of the above, your outer task happens to execute on the UI thread, it won't really trip you up as the inner call is wrapped in Task.Run, but this probably isn't the behaviour you expect.
You are storing the result of Task.Factory.StartNew inside a task field or property. Note, however, that your Task.Factory.StartNew call returns a Task<Task>, so the saved Task instance will transition to completed state almost immediately unless you call Unwrap on it and get to the inner task. To avoid this entire mess, just use Task.Run to create the outer task (as it has Unwrap semantics built in). If you do that, you can ditch the inner Task.Run completely, like so:
public bool IsRunning
{
get
{
return task.Status == TaskStatus.Running;
}
}
public void Start()
{
tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
task = Task.Run(async () =>
{
while (true)
{
CheckForStateChange(token);
token.ThrowIfCancellationRequested();
await Task.Delay(1000); // Pause 1 second before checking state again
}
}, token);
// Uncomment this and step through `CheckForStateChange`.
// When the execution hangs, you'll know what's causing the
// postbacks to the UI thread and *may* be able to take it out.
// task.Wait();
}
Since you have a CancellationToken you need to be passing it to CheckForStateChange, and checking it periodically - otherwise it only gets checked once, when the Task is started, and then never again.
Note that I have also provided a different IsRunning implementation. Volatile state is hard to get right. If the framework is giving it to you for free, you should use it.
Final word
Overall this entire solution feels like a bit of a crutch for something that should be done more reactively - but I can think of scenarios where this sort of design is valid. I'm just not convinced that yours is really one of them.
EDIT: how to find what's blocking the UI
I'll get downvoted to oblivion for this, but here goes:
The sure way to find what's causing postbacks to the UI thread is to deadlock with it. There's plenty of threads here on SO telling you how to avoid that, but in your case - we'll cause it on purpose and you'll know exactly what calls you need to avoid when you're polling for changes - although whether or not it will be possible to avoid these calls, remains to be seen.
I've put a task.Wait instruction at the end of my code snippet. Provided that you call Start on the UI thread, that should cause a deadlock with something inside your CheckForStateChange, and you will know what it is that you need to work around.

Calling method from DoWork handler?

I am attempting to use a BackgroundWorker to complete a task. I have got the worker to run correctly, under the DoWork method it then calls another method which executes but then I face my problem: when that method tries to call another method it does not succeed and does not throw an exception and I can only see this as being something I am doing wrong with BackgroundWorker since when running on the UI thread for testing the methods execute as intended.
Here is where I run my worker:
private void btnAddShots_Click(object sender, EventArgs e)
{
backgroundWorker.RunWorkerAsync();
}
Here is my DoWork method:
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
int noOfShots = dataGridShots.Rows.Count - 1;
int count = 0;
while (count < noOfShots)
{
addTaskPair(dataGridShots.Rows[count].Cells[0].Value.ToString(),
dataGridShots.Rows[count].Cells[1].Value.ToString(),
dataGridShots.Rows[count].Cells[2].Value.ToString());
count += 1;
}
}
Here is a stripped down version of addTaskPair method that is called by my worker:
private void addTaskPair(string taskName, string taskDescription, string taskPriority)
{
try
{
Task trackingTask = new Task();
trackingTask.content = taskName;
trackingTask.description = taskDescription;
trackingTask.priority = taskPriority;
string trackingJson = JsonConvert.SerializeObject(trackingTask);
trackingJson = "{ \"todo-item\":" + trackingJson + " }";
string jsonResponse;
jsonResponse = postJSON(trackingJson, teamworkURL + "/tasklists/"
+ todoLists.todoLists[cmbTrackingList.SelectedIndex].id + "/tasks.json");
}
catch (Exception e)
{
debugMessage(e.ToString());
}
}
Within the above sample you will see that I call the method postJSON, this is where I hit my wall. Through testing I have verified that the above method runs, but the postJSON method does not run at all when being called from within this thread.
I have seen many references to Invoking when researching this problem but they all seem to apply to changing ui controls which I do not need to do (though a progress bar is manipulated using the ProgressChanged BackgroundWorker event).
I can clarify my problem more if needs be but I am really hoping for assistance with this since I have never successfully worked with backgroundworker or threading before (I am not a professional as I'm sure you can tell from my code).
You're working with the UI controls from the BackgroundWorker.DoWork event handler. Don't do that.
Collect the data before starting the BackgroundWorker, and pass it as an argument to the RunWorkerAsync method. Do not touch the UI from the BackgroundWorker.DoWork - progress updates through the ReportProgress method are fine.
Also, if you're running on .NET 4.5+, you might want to consider using the new Task pattern instead. It still requires you to collect the data to process beforehand, but it's a lot easier to work with:
(EDIT: As Peter suggested, the invalid access is happening in cmbTrackingList.SelectedIndex; I've included it in the code below. That's exactly the reason I suggested using static methods for the operations happening in a separate thread - it makes you think a lot more about the data you're working with)
var todoList = todoLists.todoLists[cmbTrackingList.SelectedIndex];
var data =
dataGridShots
.Rows
.Select
(
i =>
new
{
TaskName = i.Cells[0].Value.ToString(),
TaskDescription = i.Cells[1].Value.ToString(),
TaskPriority = i.Cells[2].Value.ToString()
}
)
.ToArray();
var result =
await Task.Run
(
() =>
foreach (var row in data)
handleRowData(row.TaskName, row.TaskDescription, row.TaskPriority, todoList)
);
Now that you're this far, you might notice that it shouldn't be too hard to make your postJson method asynchronous as well (there's plenty of ways to make HTTP requests asynchronously) - this will allow you to make your whole code asynchronous without blocking any thread.
Multi-threading is hard. Always try to work with the highest possible abstraction, and avoid any shared state. If you do need shared state, you need to synchronize every single access to it, from each of the threads - try to avoid that (a good practice being having the methods executing on different threads static, so that you don't accidentaly touch shared state).
From your description, it seems that the access of the DataGrid components is not the problem. That is, it seems those statements get executed correctly, and the addTaskPair() method is successfully called, but the postJSON() method is not.
Given that, I suspect that the evaluation of cmbTrackingList.SelectedIndex is what's throwing an exception and interrupting the thread.
That said, the advice is still the same: keep the UI-related stuff on the UI thread, and only run the other stuff outside the UI thread. Given the code you've posted, it seems likely that the only thing that really ought to be asynchronous (i.e. run in the background, so as to not delay the UI thread too much) is the call to postJSON(). Presumably this is a synchronous network call, and so could take awhile. The other stuff should run deterministically and quickly.
Given that, here's how I'd refactor the code, taking advantage of the new async/await feature:
private async void btnAddShots_Click(object sender, EventArgs e)
{
int noOfShots = dataGridShots.Rows.Count - 1;
int count = 0;
while (count < noOfShots)
{
await addTaskPair(dataGridShots.Rows[count].Cells[0].Value.ToString(),
dataGridShots.Rows[count].Cells[1].Value.ToString(),
dataGridShots.Rows[count].Cells[2].Value.ToString());
count += 1;
}
}
private async Task addTaskPair(string taskName, string taskDescription, string taskPriority)
{
try
{
TaskData trackingTask = new TaskData();
trackingTask.content = taskName;
trackingTask.description = taskDescription;
trackingTask.priority = taskPriority;
string trackingJson = JsonConvert.SerializeObject(trackingTask);
trackingJson = "{ \"todo-item\":" + trackingJson + " }";
string jsonResponse;
string url = teamworkURL + "/tasklists/"
+ todoLists.todoLists[cmbTrackingList.SelectedIndex].id + "/tasks.json";
jsonResponse = await Task.Run(() => postJSON(trackingJson, url));
}
catch (Exception e)
{
debugMessage(e.ToString());
}
}
NOTE: in the above I changed the name of your own Task type to TaskData. I strongly recommend you pick a name other than Task, due to the pervasive use of the .NET Task type throughout the modern .NET API.
In the above, most of the code will run on the UI thread. The async methods are rewritten by the compiler to return at any await statement, and to resume execution of that method when the awaited Task has completed. Note that the async method only returns when there is finally a Task object to await; so in the above, the btnAddShots_Click() method will initially return once the addTaskPair() method has called Task.Run() and itself has returned at the await statement.
Important: in this context, calling and awaiting an asynchronous method from the UI thread causes the framework to run the rest of the method back on the UI thread. That is, when the asynchronous operation has completed, control of the execution of the code is returned back to the UI thread where you started.
It's this feature that makes all of this work correctly, so it's useful to make sure you understand it. :)
The call to postJSON() is executed in a separate thread, using the Task object that is created by the Task.Run() method. Since that will be executing on other than the UI thread, I've moved the computation of its URL argument to a local variable just before the call to Task.Run(), which variable is then passed to the postJSON() method when it's called in the task thread. Doing this ensures that the evaluation of cmbTrackingList.SelectedIndex is done in the UI thread.
EDIT:
Noting that the OP has commented he is using .NET 4 and not 4.5 (in which the async/await feature was officially released), I offer this slightly more awkward alternative, which still preserves the execution characteristics of the preferable 4.5-compatible version above. While one can install the async/await features on VS2010 (and IMHO that's a better way to go), this alternative allows for "pure" .NET 4 code while still achieving basically the same runtime result.
private void btnAddShots_Click(object sender, EventArgs e)
{
Action<Task> continuation = null;
TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
int noOfShots = dataGridShots.Rows.Count - 1;
int count = 0;
// Note that the continuation delegate is chained, attaching itself as
// the continuation for each successive task, thus achieving a looping
// mechanism.
continuation = task =>
{
if (count < noOfShots)
{
addTaskPair(dataGridShots.Rows[count].Cells[0].Value.ToString(),
dataGridShots.Rows[count].Cells[1].Value.ToString(),
dataGridShots.Rows[count].Cells[2].Value.ToString())
.ContinueWith(continuation, uiScheduler);
count += 1;
}
}
// Invoking the continuation delegate directly gets the ball rolling
continuation(null);
}
private Task addTaskPair(string taskName, string taskDescription, string taskPriority)
{
try
{
TaskData trackingTask = new TaskData();
trackingTask.content = taskName;
trackingTask.description = taskDescription;
trackingTask.priority = taskPriority;
string trackingJson = JsonConvert.SerializeObject(trackingTask);
trackingJson = "{ \"todo-item\":" + trackingJson + " }";
string url = teamworkURL + "/tasklists/"
+ todoLists.todoLists[cmbTrackingList.SelectedIndex].id + "/tasks.json";
// NOTE: must explicitly specify TaskScheduler.Default, because
// the default scheduler in the context of a Task is whatever the
// current scheduler is, which while executing a continuation would
// be the UI scheduler, not TaskScheduler.Default.
return Task.Factory.StartNew(() => postJSON(trackingJson, url),
CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default)
.ContinueWith(task =>
{
if (task.Exception != null)
{
// Task exceptions are wrapped in an AggregateException
debugMessage(task.Exception.InnerException.ToString());
}
else
{
string jsonResponse = task.Result;
// do something with jsonResponse?
}
}, TaskScheduler.FromCurrentSynchronizationContext());
}
catch (Exception e)
{
debugMessage(e.ToString());
}
}
Notes:
The task scheduler for the current synchronization context is used for each continuation. This ensures that the continuation itself is executed back on the UI thread, where you can safely interact with UI objects.
The most awkward part of this IMHO is that your while loop becomes an if statement, since the execution of the loop spans multiple method invocations.
While async/await would normally allow normal exception-handling syntax, when using explicit continuations you don't have this option. But Task will wrap any exceptions that occur in an AggregateException instance, which you can use to get at the real exception and report it.
TaskFactory.StartNew() has a somewhat subtle behavior: the scheduler it uses to run the given task is TaskScheduler.Current. The first time the addTaskPair() method is called, there is no current task, and so TaskScheduler.Current returns the default (i.e. thread pool) scheduler. But every subsequent time the addTaskPair() method is called, it is from within a task continuation, and so TaskScheduler.Current would return the scheduler being used to execute the continuation. Of course, we've intentionally made this the UI scheduler and running the new postJSON() task using that scheduler would defeat the purpose, as it would just execute synchronously on the current thread. So it's imperative here to specify the scheduler we want, i.e. TaskScheduler.Default, corresponding to the thread pool scheduler.
Without async/await, it's a bit harder to get things just right. It's syntactically more verbose, but IMHO it's still a reasonably good option, as it substantially preserves the desired, imperative structure of the code. In particular, you get to keep the flow of execution in the UI thread, making access of UI objects trivial, and forking off the long-running operation(s) only as needed.
(I should also point out that this .NET 4 version is not strictly exactly what the compiler would generate for you when using async/await. It's very similar, but not quite the same. Also, I will point out that while implementing a method with a single await in this way is not too bad, it gets a bit out of hand if you want multiple continuations in the same method. It's possible, but at that point I'd think the urge to just upgrade to the latest version of C# would be very compelling :) ).
Finally…
If after all of the above, you want to stick with BackgroundWorker, it should be possible to avoid the exception that's occurring with a relatively simple change to your code:
int selectedIndex = (int)Invoke((Func<int>)(() => cmbTrackingList.SelectedIndex));
jsonResponse = postJSON(trackingJson, teamworkURL + "/tasklists/"
+ todoLists.todoLists[selectedIndex].id + "/tasks.json");
I.e. just use the Control.Invoke() method to invoke on the UI thread an anonymous method that will return the value of cmbTrackingList.SelectedIndex. The Control.Invoke() method will receive the returned value, and will in turn return it to you. Since Control.Invoke() is general-purpose, its object return type must be cast to the type you know is being returned.
This ensures that the cmbTrackingList object is accessed only on the UI thread. I'll also note that if this index is not expected to change while the background processing is going on (or is specifically not supposed to), then yet another alternative would be to retrieve the value in your btnAddShots_Click() method and then pass it down to the DoWork event handler, which in turn would pass it to the addTaskPair() method where it's needed.
I put this option last because I really believe that learning the async/await feature is important and worthwhile, and that while the BackgroundWorker class has served us well over the years, it's essentially been deprecated by the new features. But I also readily admit that BackgroundWorker is still a fine way to do things, and can be made to work in your scenario.

How to create a DispatcherObject asynchronously with async/await?

Clearly, there is something I am not understandig with async/await.
What is wrong with the following code? It creates the FDecoder object in an async task. But after that, whenever I try to access the FDecoder field I get an InvalidOperation exception stating that the object is owned by another thread. I thought that's the cool thing about await, that i get the results back into the calling thread...?
//could take very long for image from web
private Task<GifBitmapDecoder> OpenFileTask(string filename, bool forceReload = false)
{
return Task.Run(() =>
{
return new GifBitmapDecoder(new Uri(filename, UriKind.RelativeOrAbsolute), forceReload ? BitmapCreateOptions.IgnoreImageCache : BitmapCreateOptions.None, BitmapCacheOption.Default);
});
}
GifBitmapDecoder FDecoder;
public async void OpenFileAsync(string filename, bool forceReload = false)
{
FDecoder = await OpenFileTask(filename, forceReload);
OpenCompleted(); // do stuff with FDecoder field, throws invalid thread exception
}
EDIT:
Ok, what i found out is that the actual GifBitmapDecoder object the Task creates is a DispatcherObject which has thread affinity. This is the main problem... It appears that the only way is to get all needed data out of the Dispatcher object in the async task and pass back a normal object without thread affinity. But if anyone knows a better method, please tell me.
You always end up back in the same context, but not all contexts are tied to a single thread. Notably, the Thread Pool context treats all thread pool threads as being equal.
But I don't think that that's the specific issue here - you're using Task.Run() which is meant to run code in the thread pool. So even if your await switches everything back into the UI context, it doesn't matter because you run some of the code, explicitly, in the thread pool.
This is an interesting problem, because (as you rightly point out) GifBitmapDecoder inherits from DispatcherObject. This means it has an implementation which does not allow just any thread to invoke its operations.
To work with any DispatcherObject you should make calls through its Dispatcher property. The returned Dispatcher object lets you schedule delegates against the real object in a way that's compatible with its internal threading model via InvokeAsync:
var decoder = new GifBitmapDecoder(...);
var operation = decoder.Dispatcher.InvokeAsync(() => { }); // Do things here!
This pattern, rather than returning a TPL Task returns a DispatcherOperation (presumably because it pre-dates TPL). This very task-like object lets you examine the state of the operation and get any results. It's also awaitable, meaning you can use it with await just like a TPL Task:
await decoder.Dispatcher.InvokeAsync(() => { });
In your specific problem, you should use this pattern in your OpenCompleted() method. You will probably want to make it OnCompletedAsync() and return a Task to enable you to capture the UI Synchronization Context for your continuations and let the TPL handle marshalling calls back from the Dispatcher to the UI thread.
public async void OpenFileAsync(string filename, bool forceReload = false)
{
FDecoder = await OpenFileTask(filename, forceReload);
await OpenCompletedAsync();
}
Task.Run() schedules to the threadpool, so your GifBitmapDecoder is being created on a different thread.
OpenFileTask is returning a task<GifBitMapDecoder>. You will likely need
Task <GifBitMapDecoder> t = OpenFileTask();
Fdecoder = t.result; //Returns the GifBitMapDecoder object.
Don't know much about the async stuff though, but probably is the same as you have it.
Source:C#5.0 in a nutshell.

Categories