C#5中异步模式的一个缺点是任务不是协变,I.E没有ITask<out TResult>
我注意到我的开发人员经常这样做
return await SomeAsyncMethod();
来解决这个问题。
这个箱子对性能有何影响?没有I / O或线程产量它只会等待并将其转换为正确的协变,在这种情况下异步框架会做什么?会有任何线程上下文切换吗?
编辑:更多信息,此代码不会编译
public class ListProductsQueryHandler : IQueryHandler<ListProductsQuery, IEnumerable<Product>>
{
private readonly IBusinessContext _context;
public ListProductsQueryHandler(IBusinessContext context)
{
_context = context;
}
public Task<IEnumerable<Product>> Handle(ListProductsQuery query)
{
return _context.DbSet<Product>().ToListAsync();
}
}
beacuse任务不是协变但添加等待它会将其强制转换为IEnumerable而不是ToListAsync返回的List
ConfigureAwait(false)
域代码中的任何地方都不是一个可行的解决方案,但我肯定会将它用于我的低级方法,如
public async Task<object> Invoke(Query query)
{
var dtoType = query.GetType();
var resultType = GetResultType(dtoType.BaseType);
var handler = _container.GetInstance(typeof(IQueryHandler<,>).MakeGenericType(dtoType, resultType)) as dynamic;
return await handler.Handle(query as dynamic).ConfigureAwait(false);
}
答案 0 :(得分:2)
解决此问题的方法成本越显着,如果SynchronizationContext.Current
中有值,则需要向其发布值并等待它安排该工作。如果上下文忙于完成其他工作,那么当您实际需要在该上下文中执行任何操作时,您可能会等待一段时间。
只需使用ConfigureAwait(false)
即可避免这种情况,同时仍保留方法async
。
一旦删除了使用同步上下文的可能性,那么async
方法生成的状态机的开销不应高于添加延续时需要提供的开销。明确。
答案 1 :(得分:2)
有一些开销相关联,虽然大多数情况下,不会有上下文切换 - 大部分成本都在为等待的每个异步方法生成的状态机中。主要问题是如果有什么东西阻止继续同步运行。而且由于我们正在讨论I / O操作,实际I / O操作的开销往往会相形见绌 - 同样,主要的例外是Winforms使用的同步上下文。
为了减少这种开销,你可以自己做一个辅助方法:
public static Task<TOut> Cast<TIn, TOut>(this Task<TIn> @this, TOut defaultValue)
where TIn : TOut
{
return @this.ContinueWith(t => (TOut)t.GetAwaiter().GetResult());
}
(defaultValue
参数仅用于类型推断 - 遗憾的是,您必须明确地写出返回类型,但至少您不必手动输入“输入”类型)
样本用法:
public class A
{
public string Data;
}
public class B : A { }
public async Task<B> GetAsync()
{
return new B { Data =
(await new HttpClient().GetAsync("http://www.google.com")).ReasonPhrase };
}
public Task<A> WrapAsync()
{
return GetAsync().Cast(default(A));
}
如果需要,您可以尝试进行一些调整,例如:使用TaskContinuationOptions.ExecuteSynchronously
,这应该适用于大多数任务调度程序。
答案 2 :(得分:1)
这个箱子对性能的影响究竟是什么?
几乎没有。
是否有任何线程上下文切换?
不过是正常的。
此对话中的另一个重要元素是task continuations execute synchronously if possible(再次,在我的博客中描述)。请注意,这实际上并未在任何地方记录;它是一个实施细节。
ConfigureAwait(false)
域代码中的任何地方都不是一个可行的解决方案
您可以在您的域代码中使用ConfigureAwait(false)
(出于语义原因,我实际上建议您这样做),但在这种情况下,它可能不会在性能方面产生任何差异。这就是为什么......
让我们考虑一下这个调用在您的应用程序中是如何工作的。毫无疑问,您获得了一些依赖于上下文的入门级代码 - 例如,按钮单击处理程序或ASP.NET MVC操作。这会调用有问题的代码 - 执行&#34;异步转换&#34;的域代码。这反过来调用已经使用ConfigureAwait(false)
的低级代码。
如果您在域代码中使用ConfigureAwait(false)
,则完成逻辑将如下所示:
如果您不在您的域代码中使用ConfigureAwait(false)
,则完成逻辑将如下所示:
所以,这只是上下文切换何时发生的问题。对于UI应用程序,尽可能长时间地在线程池上保留少量工作是很好的,但对于大多数应用程序而言,如果你不这样做,它将不会损害性能。同样,对于ASP.NET应用程序,如果您在请求上下文中保留尽可能多的代码,则可以免费获得少量并行性,但对于大多数应用程序而言,这无关紧要。
答案 3 :(得分:0)
编译器生成一个状态机,在调用方法时保存当前SynchornizationContext
并在await
之后恢复它。
因此可能存在上下文切换,例如当您从UI线程调用此函数时,await
之后的代码将切换回在UI线程上运行,这将导致上下文切换。
这篇文章可能很有用Asynchronous Programming - Async Performance: Understanding the Costs of Async and Await
在您的情况下,在等待之后粘贴ConfigureAwait(false)
以避免上下文切换可能很方便,但是async
方法的状态机仍将生成。最后,与您的查询费用相比,await
的费用可以忽略不计。