WPF Dispatcher has a method InvokeAsync that accepts an Action
or a Func<T>
and DispatcherPriority
.
I need to pass Func<Task<T>>
and I need to run whole Task<T>
on a WPF Dispatcher with a given priority.
Example:
I need to call an async function Foo
that has to run on Dispatcher thread:
async Task<SomeResult> Foo()
{
// some view model modification that needs to run on UI thread
...
// calling some IO call on thread pool
await Task.Run(() => SomeIoCallAsync());
// more view model modifications
...
// calling something on thread pool
await Task.Run(() => HeavyComputation());
// more view model modifications
...
return someResult;
}
from a async function Bar
that is not running on UI thread:
async Task<SomeOtherResult> Bar()
{
...
TODO call Foo() on UI thread
...
}
Note: If Foo
would not be an async function but just synchronous, Bar
could look like this:
async Task<SomeOtherResult> Bar()
{
...
await dispatcher.InvokeAsync(Foo, someDispatcherPriority);
...
}
How can I call async function on a dispatcher thread with a some priority?
I have found the answer in the source code. The actual behavior has changed in .NET Framework 4.5. The comment states:
WPF <= 4.0
aDispatcherSynchronizationContext
always usedDispatcherPriority.Normal
to satisfySynchronizationContext.Post
andSynchronizationContext.Send
calls.With the inclusion of async Task-oriented programming in .Net 4.5, we now record the priority of the
DispatcherOperation
in theDispatcherSynchronizationContext
and use that to satisfySynchronizationContext.Post
andSynchronizationContext.Send
calls. This enables async operations to "resume" after anawait
statement at the same priority they are currently running at.This is, of course, an observable change in behavior.
Test code:
void Main()
{
var d = Dispatcher.CurrentDispatcher;
LogThread("Begin");
d.InvokeAsync(async () =>
{
LogThread("Main #1");
await Task.Run(async () =>
{
LogThread("[WT] Main Task #1");
var t = await d.InvokeAsync(Foo, DispatcherPriority.ApplicationIdle);
for (var i = 0; i < 10; i++)
{
var a = i;
d.BeginInvoke(() =>
{
LogThread($"Actions {a}");
Thread.Sleep(100);
}, DispatcherPriority.Background);
}
LogThread("[WT] Main Task #2");
await t;
LogThread("[WT] Main Task #3");
});
LogThread("Main #2");
d.InvokeShutdown();
}, DispatcherPriority.Normal);
Dispatcher.Run();
LogThread("End");
}
async Task Foo()
{
LogThread("Foo #1");
await Task.Run(() =>
{
LogThread("[WT] Foo #2 Sleep");
Thread.Sleep(100);
LogThread("[WT] Foo #3 Sleep");
});
LogThread("Foo #4");
await Task.Run(async () =>
{
LogThread("[WT] Foo #5 Delay");
await Task.Delay(100);
LogThread("[WT] Foo #6 Delay");
});
LogThread("Foo #5");
}
void LogThread(string id) => Console.WriteLine($"{Environment.CurrentManagedThreadId} {GetPriority()} - {id}");
object? GetPriority()
{
var dsc = SynchronizationContext.Current as DispatcherSynchronizationContext;
if (dsc is null)
return "WT";
return typeof(DispatcherSynchronizationContext).GetField("_priority", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(dsc);
}
Output:
1 WT - Begin
1 Normal - Main #1
11 WT - [WT] Main Task #1
1 ApplicationIdle - Foo #1
11 WT - [WT] Foo #2 Sleep
10 WT - [WT] Main Task #2
1 Background - Actions 0
11 WT - [WT] Foo #3 Sleep
1 Background - Actions 1
1 Background - Actions 2
1 Background - Actions 3
1 Background - Actions 4
1 Background - Actions 5
1 Background - Actions 6
1 Background - Actions 7
1 Background - Actions 8
1 Background - Actions 9
1 ApplicationIdle - Foo #4
8 WT - [WT] Foo #5 Delay
11 WT - [WT] Foo #6 Delay
1 ApplicationIdle - Foo #5
11 WT - [WT] Main Task #3
1 Normal - Main #2
1 WT - End