17

图表 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 指向它,也不重新编译)会停止此工作。回调永远不会被调用。

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

4

1 回答 1

14

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

tl;博士解决这个问题,强制CompleteSynchronously总是返回 false (警告非演讲者)。


.Net 4.5“重大”更改的 MSDN 文档

发布此问题后,我立即点击了一个相关问题,最后看到了“.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 对 .NET 进行了更改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; }
}

它们的_isCompleted字段指示异步操作是否已完成,这很好,并且可以从 中返回 this 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是真的,因此不做任何后续工作,因为它假定调用者做了它。
  • 现在没有人运行或将运行回调。

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

于 2013-02-15T07:18:17.357 回答