[英]C# await in async method clears SynchronizationContext (with repro tests)
Using Visual Studio 2015 Update 3 and a C# Test project targeting .NET 4.6.1 I get the following behavior: 使用Visual Studio 2015 Update 3和针对.NET 4.6.1的C#测试项目,我得到以下行为:
[TestClass]
public class AwaitTests
{
[TestMethod]
public void AsyncRemovingSyncContext_PartialFail()
{
Log("1");
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
Log("2");
HasAwait().Wait(); // The continuation in this method is on wrong thread
Log("5");
Assert.IsNotNull(SynchronizationContext.Current);
}
[TestMethod]
public async Task AsyncRemovingSyncContext_Fail()
{
Log("1");
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
Log("2");
await HasAwait();
Log("5"); // Issue is here - Sync Context is now null
Assert.IsNotNull(SynchronizationContext.Current);
}
public async Task HasAwait()
{
Log("3");
await Task.Delay(300);
Log("4");
}
private void Log(string text)
{
Console.WriteLine($"{text} - Thread {System.Threading.Thread.CurrentThread.ManagedThreadId} - {SynchronizationContext.Current}");
}
}
Here is the output: 这是输出:
AsyncRemovingSyncContext_PartialFail AsyncRemovingSyncContext_PartialFail
1 - Thread 7 - 1-线程7-
2 - Thread 7 - System.Threading.SynchronizationContext 2-线程7-System.Threading.SynchronizationContext
3 - Thread 7 - System.Threading.SynchronizationContext 3-线程7-System.Threading.SynchronizationContext
4 - Thread 8 - 4-线8-
5 - Thread 7 - System.Threading.SynchronizationContext 5-线程7-System.Threading.SynchronizationContext
AsyncRemovingSyncContext_Error AsyncRemovingSyncContext_Error
1 - Thread 7 - 1-线程7-
2 - Thread 7 - System.Threading.SynchronizationContext 2-线程7-System.Threading.SynchronizationContext
3 - Thread 7 - System.Threading.SynchronizationContext 3-线程7-System.Threading.SynchronizationContext
4 - Thread 8 - 4-线8-
5 - Thread 8 - 5-线8-
-Assert Exception Thrown- -声明抛出异常-
I've done other tests and so far there is a 100% correlation between the existence of the await
keyword in the method and the clearing of the Sync Context. 我已经做过其他测试,到目前为止,该方法中
await
关键字的存在与Sync Context的清除之间存在100%的相关性。 This includes async lambdas. 这包括异步lambda。
This is important to me because it appears that as soon as an await
is encountered, the sync context is removed. 这对我很重要,因为看起来一旦遇到
await
,同步上下文就会被删除。 This means that if I perform two await
s in the same method, the second continuation will just run in the thread pool (default behavior when no Sync Context). 这意味着,如果我以相同的方法执行两个
await
,则第二个继续操作将仅在线程池中运行(没有Sync Context时的默认行为)。
Is this a framework/compiler bug or am I doing something wrong? 这是框架/编译器错误,还是我做错了什么?
For specifics, since I'm sure someone will ask in one form or another, I have an Active Object that I would like to enable async
\\ await
support for, but I can only do that if I can guarentee that the continuation will be dispatched to my ActiveObjectSynchronizationContext
, which at the moment, it's not because it's being cleared. 具体来说,因为我确定有人会以一种或另一种形式询问,所以我有一个Active Object ,我想启用
async
\\ await
支持,但是只有在我可以确保继续执行将被调度的情况下,我才能这样做。到我的ActiveObjectSynchronizationContext
,目前不是因为它已被清除。
I've already looked at this question (and similar about the UI context 4.0 bug) but this isn't related since I'm running 4.6.1 and I'm using non-UI threads. 我已经看过这个问题了 (与UI上下文4.0错误类似),但这无关紧要,因为我正在运行4.6.1,并且我使用的是非UI线程。
I also followed the advice from this other question and ensured my Sync Context implements CreateCopy
, but I can tell from test profiling that the method isn't even called. 我还遵循了另一个问题的建议,并确保我的Sync Context实现了
CreateCopy
,但是从测试概要文件中我可以知道甚至没有调用该方法。
new SynchronizationContext()
by convention is the same as a null
synchronization context; 按照约定,
new SynchronizationContext()
与null
同步上下文相同; that is, the thread pool context. 即线程池上下文。 So, the behavior in those tests is not unexpected.
因此,这些测试中的行为并不意外。
await
is behaving correctly; await
状态正确; it is seeing the current synchronization context and Post
ing its continuation to that context. 它是看到当前同步上下文和
Post
荷兰国际集团及其延续这方面。 However, the thread pool sync context just executes the delegate on a thread pool thread - it doesn't set SynchronizationContext.Current
to new SynchronizationContext
because the convention is that null
is the thread pool sync context. 但是,线程池同步方面只执行一个线程池线程的委托-它不设置
SynchronizationContext.Current
到new SynchronizationContext
,因为约定是null
的线程池同步上下文。
I believe the problem with your real code is that your ActiveObjectSynchronizationContext
is not setting itself as current when executing queued delegates. 我相信您的实际代码存在的问题是,在执行排队的委托时,您的
ActiveObjectSynchronizationContext
没有将自身设置为当前的。 It's the responsibility of SynchronizationContext.Post
to call SetSynchronizationContext
if necessary just before executing the delegate, although - correct me if I'm wrong - the name "Active Object" seems to imply a single-threaded STA-like model. SynchronizationContext.Post
有责任在执行委托之前在必要时调用SetSynchronizationContext
,但是-如果我错了,请纠正我-名称“ Active Object”似乎暗示着一个类似于单线程STA的模型。 If this is the case, then Post
wouldn't need to set it; 如果是这种情况,则
Post
无需进行设置; it would just need to execute the delegate on the correct thread, which should already have the correct current sync context. 它只需要在正确的线程上执行委托,该线程应该已经具有正确的当前同步上下文。
If it helps, I have a single-threaded SynchronizationContext
here , though it doesn't do any kind of (explicit) message pumping. 如果有帮助,我有一个单线程
SynchronizationContext
在这里 ,但它不会做任何形式的(明确的)消息抽水。
As @StephenCleary mentioned in a comment, the issue was that in my real code I was setting the Sync Context in an async method but before the await keyword, which I thought would work but apparently it doesn't (I'm guessing the state machine grabs the context at the start of the method?). 正如@StephenCleary在评论中提到的那样,问题在于,在我的真实代码中,我在async方法中但在await关键字之前设置了Sync Context,我认为这是可行的,但显然没有用(我猜测状态机器在方法开始时获取上下文?)。
The solution is to set the Sync Context in a non-async method first. 解决方案是首先以非异步方法设置同步上下文。
activeObject.Enqueue(async () =>
{
SynchronizationContext
.SetSynchronizationContext(
new ActiveObjectSynchronizationContext(activeObject));
// do work
await Task.Delay(500);
// Sync Context now cleared
});
activeObject.Enqueue(() =>
SynchronizationContext
.SetSynchronizationContext(
new ActiveObjectSynchronizationContext(activeObject));
);
activeObject.Enqueue(async () =>
{
// do work
await Task.Delay(500);
// Still on Active Object thread
});
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.