库中的 ConfigureAwait(false) 是否会丢失调用应用程序的同步上下文?
Can ConfigureAwait(false) in a library lose the synchronization context for the calling application?
我从比我聪明的人那里读过很多次建议,其中很少有注意事项:始终在库代码中使用 ConfigureAwait(false)
。所以我相当确定我知道答案,但我想成为 100%。场景是我有一个库,它薄薄地包装了一些其他异步库。
图书馆代码:
public async Task DoThingAsyc() {
// do some setup
return await otherLib.DoThingAsync().ConfigureAwait(false);
}
申请代码:
// need to preserve my synchronization context
await myLib.DoThingAync();
// do I have my context here or did my lib lose it?
没有
SynchronizationContext
的捕获发生在 await
。 ConfigureAwait
配置特定的 await
.
如果应用程序调用库的异步方法并等待它,则无论调用内部发生什么,SC 都会被当场捕获。
现在,因为异步方法的同步部分(即第一个 await
之前的部分)是在 return 等待任务之前执行的,所以您可以乱用 SynchronizationContext
在那里,但是 ConfigureAwait
没有那样做。
在您的具体示例中,您似乎是 returning 来自异步方法的 ConfigureAwait
的结果。这不可能发生,因为 ConfigureAwait
return 是 ConfiguredTaskAwaitable
结构。但是,如果我们更改方法 return 类型:
public ConfiguredTaskAwaitable DoThingAsyc()
{
return otherLib.DoThingAsync().ConfigureAwait(false);
}
那么等待它确实会影响调用代码的等待行为。
示例来自 http://blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx
... logically you can think of the following code:
await FooAsync();
RestOfMethod();
as being similar in nature to this:
var t = FooAsync();
var currentContext = SynchronizationContext.Current;
t.ContinueWith(delegate
{
if (currentContext == null)
RestOfMethod();
else
currentContext.Post(delegate { RestOfMethod(); }, null);
}, TaskScheduler.Current);
这意味着您应该在 await myLib.DoThingAync();
调用后恢复上下文。
如果在异步调用的逻辑链中使用不一致,ConfigureAwait(false)
可能会添加冗余的上下文切换(这通常意味着冗余的线程切换)。这可能发生在存在同步上下文的情况下,当逻辑堆栈上的一些异步调用使用 ConfigureAwait(false)
而有些不使用(更多 )时。
您仍然应该在您的代码中使用 ConfigureAwait(false)
,但您可能想查看您正在调用的第 3 方代码并减少与以下内容的任何不一致:
public async Task DoThingAsyc() {
// do some setup
await Task.Run(() => otherLib.DoThingAsync()).ConfigureAwait(false);
// do some other stuff
}
这会增加一个额外的线程切换,但可能会阻止许多其他线程切换。
此外,如果您正在创建一个非常薄的包装器,就像您展示的那样,您可能希望像下面那样实现它,根本没有 async/await
:
public Task DoThingAsyc() {
// do some setup
return otherLib.DoThingAsync();
}
我从比我聪明的人那里读过很多次建议,其中很少有注意事项:始终在库代码中使用 ConfigureAwait(false)
。所以我相当确定我知道答案,但我想成为 100%。场景是我有一个库,它薄薄地包装了一些其他异步库。
图书馆代码:
public async Task DoThingAsyc() {
// do some setup
return await otherLib.DoThingAsync().ConfigureAwait(false);
}
申请代码:
// need to preserve my synchronization context
await myLib.DoThingAync();
// do I have my context here or did my lib lose it?
没有
SynchronizationContext
的捕获发生在 await
。 ConfigureAwait
配置特定的 await
.
如果应用程序调用库的异步方法并等待它,则无论调用内部发生什么,SC 都会被当场捕获。
现在,因为异步方法的同步部分(即第一个 await
之前的部分)是在 return 等待任务之前执行的,所以您可以乱用 SynchronizationContext
在那里,但是 ConfigureAwait
没有那样做。
在您的具体示例中,您似乎是 returning 来自异步方法的 ConfigureAwait
的结果。这不可能发生,因为 ConfigureAwait
return 是 ConfiguredTaskAwaitable
结构。但是,如果我们更改方法 return 类型:
public ConfiguredTaskAwaitable DoThingAsyc()
{
return otherLib.DoThingAsync().ConfigureAwait(false);
}
那么等待它确实会影响调用代码的等待行为。
示例来自 http://blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx
... logically you can think of the following code:
await FooAsync(); RestOfMethod();
as being similar in nature to this:
var t = FooAsync(); var currentContext = SynchronizationContext.Current; t.ContinueWith(delegate { if (currentContext == null) RestOfMethod(); else currentContext.Post(delegate { RestOfMethod(); }, null); }, TaskScheduler.Current);
这意味着您应该在 await myLib.DoThingAync();
调用后恢复上下文。
如果在异步调用的逻辑链中使用不一致,ConfigureAwait(false)
可能会添加冗余的上下文切换(这通常意味着冗余的线程切换)。这可能发生在存在同步上下文的情况下,当逻辑堆栈上的一些异步调用使用 ConfigureAwait(false)
而有些不使用(更多
您仍然应该在您的代码中使用 ConfigureAwait(false)
,但您可能想查看您正在调用的第 3 方代码并减少与以下内容的任何不一致:
public async Task DoThingAsyc() {
// do some setup
await Task.Run(() => otherLib.DoThingAsync()).ConfigureAwait(false);
// do some other stuff
}
这会增加一个额外的线程切换,但可能会阻止许多其他线程切换。
此外,如果您正在创建一个非常薄的包装器,就像您展示的那样,您可能希望像下面那样实现它,根本没有 async/await
:
public Task DoThingAsyc() {
// do some setup
return otherLib.DoThingAsync();
}