.Net 4.5杀了我的TPL,现在怎么办?

Ben*_*jol 17 c# task-parallel-library .net-4.5

图表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);
}
Run Code Online (Sandbox Code Playgroud)

安装.Net 4.5(不指向VS,也不重新编译)会阻止这种工作.永远不会调用回调.

任何想法可能导致这种情况,否则,我可以做些什么来尝试进一步缩小问题的根本原因或解决问题?

Ben*_*jol 14

重新编辑:我和Stephen Toub交换了几封电子邮件.下面我尝试将我原来的答案和他的答案合并成一个连贯的整体.

tl; dr来解决这个问题,强迫CompleteSynchronously总是返回false(告诫非lector).


MSDN文档.Net 4.5'破坏'更改

在发布此问题后,我立即点击了相关问题,最后选择了".NET Framework 4.5中的应用程序兼容性",其中有以下内容FromAsync:

更改:IAsyncResult实现必须同步完成,并且其CompletedSynchronously属性必须返回true 才能完成生成的任务.

影响:如果IAsyncResult实现未完成同步执行但其 CompletedSynchronously属性返回True,则生成的任务将无法完成.

具有讽刺意味的是,(或令人愤怒的),国家的页面CompletedSynchronously:

对实施者的说明:IAsyncResult接口的大多数实现者都不会使用此属性,并且应该返回false.


Stephen Toub用以下内容澄清了这一点:

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返回 trueIAsyncResult尚未完成,则有一个需要修复的错误,并且Task返回的可能FromAsync 无法正确完成.

此更改是出于性能原因.


回到我的问题代码

这是他非常有帮助的分析,我将其全部包含在内,因为它可能对其他实施者有用IAsyncResult:

问题似乎是你正在使用的库有一个非常错误的实现IAsyncResult; 特别是,它的实施CompletedSynchronously不正确.这是他们的实施:

public bool CompletedSynchronously
{
    get { return _isCompleted; }
}
public bool IsCompleted
{
    get { return _isCompleted; }
}
Run Code Online (Sandbox Code Playgroud)

它们的_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
}
Run Code Online (Sandbox Code Playgroud)

请注意,调用者和回调都使用相同的 CompletedSynchronously属性来确定哪些运行回调.因此,CompletedSynchronously必须始终为此特定实例返回相同的值.如果没有,则很容易导致错误的行为.例如,他们的实现 CompletedSynchronously返回相当于IsCompleted.想象一下以下一系列事件:

  • BeginXx 被调用并启动异步操作
  • BeginXx返回其调用者,该调用者检查CompletedSynchronously是错误的,因为操作尚未完成.
  • 现在操作完成并调用回调.回调看到这CompletedSynchronously是真的,因此不会做任何后续工作,因为它假定调用者做了它.
  • 现在没有人跑或者会回调.

简而言之,图书馆有一个错误.如果你改为 CompletedSynchronously返回true,你掩盖了这个问题,但你可能会引起另一个问题:如果调用者(在你的情况下FromAsync)认为操作已经完成,它将继续立即调用EndXx方法,这将阻塞直到异步操作已完成,因此您已将异步操作转换为同步操作.您是否尝试过始终返回false CompletedSynchronously而不是始终返回true?