Background Tasks in Async Methods
In the next release of Halfwit RT (what, you hadn't heard of Halfwit RT?) you'll be able to start typing a username and have it auto-complete from a known list of users.
To populate this list initially, I wanted to get a list of all the people you follow as soon as you're authenticated. However, the act of fetching the users you follow takes some time, so I didn't want to "await" the call and prevent the authentication method from finishing until it was done.
The normal way to start a "fire and forget" background operation is to use Task.Factory.StartNew()
, so at first I did something like this:
Task.Factory.StartNew(async () => /* fetch user names */);
This worked, but it raised a compiler warning:
Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.
This makes perfect sense - the compiler doesn't think you want to start a background task and not wait for it to finish inside an async
method.
So how did I get around it? I got tricky. Maybe too tricky, as I'm sure there's a different (and possibly better) way to do this.
await Task.Factory.StartNew(() =>
{
Task.Factory.StartNew(async () => { /* fetch user names */ });
});
So I'm awaiting a task that effectively finishes immediately, because all it does is start a background operation in another task.
It got a little trickier than that, though, because when the act of fetching the user names is complete, I need to add them to a List<String>
, and I need that to happen on the primary thread to avoid issues. So I had to pass in the current TaskScheduler and make sure the continuation happened there.
Here's the code in its entirety, as called from my authentication method:
Task LoadFriendUserNamesAsync()
{
return Task.Factory.StartNew(scheduler =>
{
Task.Factory.StartNew(async () =>
{
var friendsResponse = await Client.GetFriendIdsAsync();
if (friendsResponse.StatusCode != System.Net.HttpStatusCode.OK) return new string[0];
var usersResponse = await Client.LookupUsersAsync(friendsResponse.Result);
if (usersResponse.StatusCode != System.Net.HttpStatusCode.OK) return new string[0];
return usersResponse.Result.Select(u => u.ScreenName).ToArray();
}).ContinueWith(t =>
{
if (t.IsFaulted || t.IsCanceled) return;
var task = t.Result;
if (task.IsFaulted || task.IsCanceled) return;
foreach (var user in task.Result) UserNames.Add(user);
}, (TaskScheduler)scheduler);
}, TaskScheduler.Current);
}
Trackbacks
- Dew Drop – April 2, 2013 (#1,518) | Alvin Ashcraft's Morning Dew | http://www.alvinashcraft.com/2013/04/02/dew-drop-april-2-2013-1518/
- Dew Drop – April 3, 2013 (#1,519) | Alvin Ashcraft's Morning Dew | http://www.alvinashcraft.com/2013/04/03/dew-drop-april-3-2013-1519/
- rimonabantexcellence site title | http://www.rimonabantexcellence.com/t.php?ahr0cdovl21hdhroyw1pbhrvbi5uzxqvymfja2dyb3vuzc10yxnrcy1pbi1hc3luyy1tzxrob2rzp3jldmlzaw9upte=
No new comments are allowed on this post.
Comments
brian dunnington
I usually like to use
Task.Run()
instead ofTask.Factory.StartNew()
(the difference is explained here by the all-knowing Stephen Toub), but it doesnt really matter in this case.If you want to kick off a 'fire-and-forget' async task and avoid the compiler error, you can just set the return result to a variable like so:
If you have the most super strict code analysis rules enabled, that can result in a new warning: CA1804 Remove Unused locals (since 'x' is assigned but never used). If you are going to do x.ContinueWith(), then that avoids the issue, but I created a tiny helper method that takes a Task and returns void so I can avoid the compiler warning and the code analysis warning:
Matt Hamilton
This is why I create blog posts like this, Brian! Thanks for your solution!
I had no idea that simply assigning a task to a variable would suppress the warning. That's great to know.
I'll also do some reading on
Task.Factory.StartNew
vsTask.Run
, so thanks for that too!brian dunnington
yeah, I guess the MS guys figure that if you are assigning the return value, then you must know what you are doing =) (it really does seem like a strange delineation to make, but who am I to question their wisdom?)
I like Task.Run() - if nothing else, it is shorter to type each time!