[英].Net 4.5 killed my TPL, now what?
图表1:将Async(非async
!)网络调用包装成Task
一些代码
public static Task<byte[]> GetAsync(IConnection connection, uint id)
{
ReadDataJob jobRDO = new ReadDataJob();
//No overload of FromAsync takes 4 extra parameters, so we have to wrap
// Begin in a Func so that it looks like it takes no parameters except
// callback and state
Func<AsyncCallback, object, IAsyncResult> wrapped = (callback, state) =>
jobRDO.Begin(connection, 0, 0, id, callback, state);
return Task<byte[]>.Factory.FromAsync(wrapped, ar =>
{
ErrorCode errorCode;
UInt32 sError;
UInt32 attribute;
byte[] data = new byte[10];
jobRDO.End(out errorCode, out sError, out attribute, out data);
if(error != ErrorCode.NO_ERROR) throw new Exception(error.ToString());
return data;
}, jobRDO);
}
安装.Net 4.5(不指向VS,也不重新编译)会阻止这种工作。 永远不会调用回调。
任何想法可能导致这种情况,否则,我可以做些什么来尝试进一步缩小问题的根本原因或解决问题?
重新编辑 :我和Stephen Toub交换了几封电子邮件。 下面我尝试将我原来的答案和他的答案合并成一个连贯的整体。
tl; dr来解决这个问题,强制CompleteSynchronously
总是返回false( 告诫非lector )。
发布此问题后,我立即点击了相关问题 ,最后选择了“.NET Framework 4.5中的应用程序兼容性” ,其中有关于FromAsync
:
更改 :
IAsyncResult
实现必须同步完成,并且其CompletedSynchronously
属性必须返回true才能完成生成的任务。影响 :如果
IAsyncResult
实现未完成同步执行但其CompletedSynchronously
属性返回True,则生成的任务将无法完成。
具有讽刺意味的是,(或令人愤怒的), CompletedSynchronously
的页面指出:
对实施者的说明 :
IAsyncResult
接口的大多数实现者都不会使用此属性,并且应该返回false 。
http://msdn.microsoft.com/en-us/library/hh367887%28v=VS.110%29.aspx#core上的表格,特别是“更改”的描述,是错误的(...) 。
.NET 4.5对
FromAsync
进行了更改,但并非所有IAsyncResult.CompletedSynchronously
实现都必须返回true:这没有任何意义。 变化是FromAsync
实际上现在正在查看IAsyncResult's CompletedSynchronously
(它在.NET 4中根本没有看到它),因此它期望它是准确的。 因此,如果你有一个错误的IAsyncResult
实现,FromAsync
可能仍然在.NET 4中工作,而使用.NET 4.5,它不太可能使用错误的实现。具体来说,如果
IAsyncResult.CompletedSynchronously
返回false
。 但是,如果它返回true
,则IAsyncResult
实际上必须同步完成。 如果CompletedSynchronously
返回true
但IAsyncResult
尚未完成,则您有一个需要修复的错误,并且从FromAsync
返回的Task
可能无法正确完成。此更改是出于性能原因。
这是他非常有用的分析,我将其全部包含在内,因为它可能对IAsyncResult
其他实现者有用:
问题似乎是你正在使用的库有一个非常错误的
IAsyncResult
实现; 特别是,它正在错误地实现CompletedSynchronously
。 这是他们的实施:public bool CompletedSynchronously { get { return _isCompleted; } } public bool IsCompleted { get { return _isCompleted; } }
它们的
_isCompleted
字段指示异步操作是否已完成,这很好,并且可以从IsCompleted
返回它,因为该属性用于指示操作是否已完成。 但CompletedSynchronously
不能只返回相同的字段:CompletedSynchronously
需要返回操作是否同步完成,即它是否在调用BeginXx
期间完成,并且它必须始终为给定的IAsyncResult
实例返回相同的值。考虑如何使用
IAsyncResult.CompletedSynchronously
的标准模式。 其目的是允许BeginXx
的调用者继续执行后续工作,而不是由于工作而进行回调。 这对于避免堆栈潜水尤为重要(想象一下所有实际同步完成的一系列异步操作:如果回调处理了所有工作,那么每个回调都会启动下一个操作,其回调将启动下一个操作,但是因为它们是同步完成的,它们的回调也将作为BeginXx
方法的一部分同步调用,因此每次调用都会在堆栈上越来越深,直到它可能溢出):IAsyncResult ar = BeginXx(…, delegate(IAsyncResult iar) => { if (iar.CompletedSynchronously) return; … // do the completion work, like calling EndXx and using its result }, …); if (ar.CompletedSynchronously) { … // do the completion work, like calling EndXx and using its result }
请注意,调用者和回调都使用相同的
CompletedSynchronously
属性来确定哪些运行回调。 因此,CompletedSynchronously
必须始终为此特定实例返回相同的值。 如果没有,则很容易导致错误的行为。 例如,它们的实现具有CompletedSynchronously
返回等效的IsCompleted
。 想象一下以下一系列事件:
BeginXx
并启动异步操作BeginXx
返回其调用者,该调用者检查CompletedSynchronously
,这是错误的,因为操作尚未完成。- 现在操作完成并调用回调。 回调看到
CompletedSynchronously
为true,因此不执行任何后续工作,因为它假定调用者执行了此操作。- 现在没有人跑或者会回调。
简而言之,图书馆有一个错误。 如果您更改了
CompletedSynchronously
以返回true,则屏蔽了此问题,但您可能会导致另一个问题:如果调用者(在您的情况下,FromAsync
)认为操作已经完成,它将立即调用EndXx
方法,这将阻止,直到异步操作完成,因此您已将异步操作转换为同步操作。 您是否尝试过始终从CompletedSynchronously
返回false而不是始终返回true?
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.