Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the proper usage of JoinableTaskFactory.RunAsync?

I searched online but there is very little information regarding ThreadHelper.JoinableTaskFactory.RunAsync

If I have the following code, Test1 runs on MainThread:

public bool Test1()
{
    // Do something here
    ThreadHelper.JoinableTaskFactory.RunAsync(this.Test2);
    // Do something else
    return false;
}

private async Task Test2()
{
    await TaskScheduler.Default;
    // do something here    
    await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
    // do something here
}

Is it ok if the RunAsync has never been awaited on? What would happen if Test1 returns before Test2 finishes running?

like image 706
Yituo Avatar asked Apr 30 '18 21:04

Yituo


2 Answers

Is it ok if the RunAsync has never been awaited on?

That depends. It's OK from JoinableTaskFactory's perspective. All the necessary continuations will continue -- it's just that your calling method won't wait for it to complete, which is the whole point of not awaiting it if you choose to do so.

But in general, it may not be healthy for your app. Consider the case that your async work is saving a file (or transmitting something over the network) and the user closes your app. Wouldn't you want the app to wait for it to finish before closing? As @GrzegorzSmulko said in his answer, the JoinableTaskFactory prescribes a pattern for blocking on shutdown (or disposal of your object) to ensure async work completes.

There's another reason to track your async work if you're in an app that hosts the CLR and shuts it down before exiting: you don't want managed threads running around doing arbitrary things when the AppDomain is being finalized or you'll find that your app crashes on shutdown. This is not a concern when you have a pure-managed app though, since it just exits without shutting down the CLR. It will not crash, but it will still abandon any half-done work.

All the foregoing is true in any app that you use JoinableTaskFactory for. If you happen to be using it within Visual Studio (I'm speaking generally here for a broader audience... I know your question specifically mentioned VS) then the rules are stressed more. You should track all your async work as prescribed in that section. You shouldn't have any "fire and forget" work.

The FileAndForget extension method is actually intended for internal Microsoft use since it sends errors to our telemetry servers. If you really want to just forget stuff, you can use the .Forget() extension method. But remember you should only use that after scheduling the work using an AsyncPackage.JoinableTaskFactory instance or another one that is tracking your async work for disposal. Don't use it on ThreadHelper.JoinableTaskFactory because that doesn't track async-and-forgotten work. So for example, don't do this:

ThreadHelper.JoinableTaskFactory.RunAsync(async () => { /* something async */ }).Forget();

The problem with the above is that the async work will not be tracked, and thus not block shutdown. You should do this instead:

myAsyncPackage.JoinableTaskFactory.RunAsync(async () => { /* something async */ }).Forget();

Or even better: just await the call, in which case you can use pretty much any JTF instance:

await ThreadHelper.JoinableTaskFactory.RunAsync(async () => { /* something async */ });

But if you're in a context where you can use await, you often don't need JoinableTaskFactory.RunAsync at all, since if you can just await the code within the delegate itself. Some uncommon scenarios may require that you still track the async work with a JoinableTaskCollection where you might want to use await someJtf.RunAsync but normally you can just drop JTF use where you can naturally await your work.

like image 94
Andrew Arnott Avatar answered Sep 22 '22 03:09

Andrew Arnott


According to Threading Cookbook for Visual Studio you should use ThreadHelper.JoinableTaskFactory.RunAsync() together with FileAndForget().

The potential problem is, that FileAndForget() is not available in VS2015, but only in VS2017+.

Is it ok if the RunAsync has never been awaited on?

I think it's not ok, you should use FileAndForget. But, I don't really know what to do for VS2015.

What would happen if Test1 returns before Test2 finishes running?

This should be pretty easy to test to make sure. I assume that Test2 will just finish later "But you also should be sure your async work finishes before your object claims to be disposed."

like image 44
Grzegorz Smulko Avatar answered Sep 20 '22 03:09

Grzegorz Smulko