Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 44 additions & 3 deletions docfx/docs/threading_rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,9 +299,50 @@ The following describes how to replace the mechanism for getting to the
UI thread in a host-independent way:

You can set your own priority by creating your own derived type of
`JoinableTaskFactory` and overriding the `PostToUnderlyingSynchronizationContext`
method. This method is responsible both for initial switches to the UI
thread as well as resuming on the UI thread after a yielding await.
`JoinableTaskFactory`.

The base implementation coalesces pending callbacks so that only one driver
message at a time is queued to the underlying synchronization context. A
derived type that does not override `PostToUnderlyingSynchronizationContext`
inherits this behavior.

For backward compatibility, an override of
`PostToUnderlyingSynchronizationContext` remains fully authoritative and does
not automatically coalesce. Existing derived types may suppress a post,
redirect it, or apply semantics that the base class cannot safely assume. Such
types therefore retain their original behavior.

A derived type may explicitly opt into coalescing by routing
`PostToUnderlyingSynchronizationContext` through
`PostToUnderlyingSynchronizationContextWithCoalescing`, and overriding
`PostToUnderlyingSynchronizationContextCore` with the actual dispatcher
operation. Because `JoinableTaskFactory` is defined in another assembly, C#
requires these `protected internal` base members to be declared `protected`
when overridden:

```csharp
protected override void PostToUnderlyingSynchronizationContext(
Comment thread
AArnott marked this conversation as resolved.
SendOrPostCallback callback,
object state)
{
this.PostToUnderlyingSynchronizationContextWithCoalescing(callback, state);
}

protected override void PostToUnderlyingSynchronizationContextCore(
SendOrPostCallback callback,
object state)
{
this.UnderlyingSynchronizationContext!.Post(callback, state);
}
```

`PostToUnderlyingSynchronizationContext` is responsible both for initial
switches to the UI thread and for resuming on the UI thread after a yielding
await. When coalescing is enabled, the core method may be called once for a
sequence of pending callbacks and should only perform the underlying post; it
should not call the coalescing helper. Replace the synchronization-context post
shown above with the custom dispatcher or priority operation required by the
derived factory.

Note that the `JoinableTaskFactory` class has no default constructor, so when
implementing your own `JoinableTaskFactory`-derived type you will need to add
Expand Down
50 changes: 50 additions & 0 deletions src/Microsoft.VisualStudio.Threading/DispatcherExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#if NETFRAMEWORK || WINDOWS

using System;
using System.Globalization;
using System.Threading;
using System.Windows.Threading;

Expand Down Expand Up @@ -74,8 +75,57 @@ internal DispatcherJoinableTaskFactory(JoinableTaskFactory innerFactory, Dispatc
this.priority = priority;
}

/// <inheritdoc />
internal override void ExecutePendingUnderlyingSynchronizationContextCallback(
SendOrPostCallback callback,
object state,
ExecutionContext? executionContext)
{
if (executionContext is null)
{
callback(state);
return;
}

CultureInfo dispatcherCulture = CultureInfo.CurrentCulture;
CultureInfo dispatcherUICulture = CultureInfo.CurrentUICulture;
CultureInfo resultingCulture = dispatcherCulture;
CultureInfo resultingUICulture = dispatcherUICulture;
try
{
ExecutionContext.Run(
executionContext,
_ =>
{
CultureInfo.CurrentCulture = dispatcherCulture;
CultureInfo.CurrentUICulture = dispatcherUICulture;
try
{
callback(state);
}
finally
{
resultingCulture = CultureInfo.CurrentCulture;
resultingUICulture = CultureInfo.CurrentUICulture;
}
},
null);
}
finally
{
CultureInfo.CurrentCulture = resultingCulture;
CultureInfo.CurrentUICulture = resultingUICulture;
}
}

/// <inheritdoc />
protected internal override void PostToUnderlyingSynchronizationContext(SendOrPostCallback callback, object state)
{
this.PostToUnderlyingSynchronizationContextWithCoalescing(callback, state);
}

/// <inheritdoc />
protected internal override void PostToUnderlyingSynchronizationContextCore(SendOrPostCallback callback, object state)
{
this.dispatcher.BeginInvoke(this.priority, callback, state);
}
Expand Down
Loading
Loading