Exhibit 1: some code wrapping an Async (not async!) network call into a 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);
}
Installing .Net 4.5 (not pointing VS at it, nor recompiling) stops this working. The callback is never invoked.
Any ideas what could be causing this, and otherwise, what I can do to try and further narrow down the root cause of the problem or work around it?
Re-edit: I've exchanged several emails with Stephen Toub. Below I attempt to merge my original answer and his answers into a coherent whole.
tl;dr to work around this, force CompleteSynchronously to always return false (caveat non-lector).
Immediately after posting this question, I clicked through to a related question, and ended up at "Application Compatibility in the .NET Framework 4.5", which has this to say about FromAsync:
Change: The
IAsyncResultimplementation must complete synchronously and itsCompletedSynchronouslyproperty must return true for the resulting task to complete.Impact: The resulting task will not complete if an
IAsyncResultimplementation does not complete synchronous execution but itsCompletedSynchronouslyproperty returns True.
Ironically, (or infuriatingly), the page for CompletedSynchronously states:
Notes to Implementers: Most implementers of the
IAsyncResultinterface will not use this property and should return false.
The table at http://msdn.microsoft.com/en-us/library/hh367887%28v=VS.110%29.aspx#core, and specifically the description of the “Change”, is wrong (...).
There was a change in .NET 4.5 to
FromAsync, but it wasn’t that allIAsyncResult.CompletedSynchronouslyimplementations must return true: that wouldn’t make any sense. The change was thatFromAsyncactually looks at theIAsyncResult’s CompletedSynchronouslynow (it didn’t look at it at all in .NET 4), and thus it expects it to be accurate. As such, if you had a buggyIAsyncResultimplementation,FromAsyncmight still have worked in .NET 4, whereas with .NET 4.5, it’s less likely to work with a buggy implementation.Specifically, it’s ok if
IAsyncResult.CompletedSynchronouslyreturnsfalse. However, if it returnstrue, theIAsyncResultmust have in fact completed synchronously. IfCompletedSynchronouslyreturnstruebut theIAsyncResulthas not completed, you have a bug that needs to be fixed, and it’s likely that theTaskreturned fromFromAsyncwill not complete correctly.The change was made for performance reasons.
Here is his very helpful analysis, which I include in full as it will probably be useful for other implementers of IAsyncResult:
The problem appears to be that the library you’re using has a very faulty implementation of
IAsyncResult; in particular, it’s implementingCompletedSynchronouslyincorrectly. Here’s their implementation:public bool CompletedSynchronously { get { return _isCompleted; } } public bool IsCompleted { get { return _isCompleted; } }Their
_isCompletedfield indicates whether the asynchronous operation has completed, which is fine, and it’s fine to return this fromIsCompleted, since that property is meant to indicate whether the operation completed or not. ButCompletedSynchronouslycan’t just return that same field:CompletedSynchronouslyneeds to return whether the operation completed synchronously, i.e. whether it completed during the call toBeginXx, and it must always return the same value for a givenIAsyncResultinstance.Consider the standard pattern for how
IAsyncResult.CompletedSynchronouslyis used. Its purpose is to allow the caller ofBeginXxto continue doing the follow-on work, rather than having the callback due the work. This is particularly important for avoiding stack dives (imagine a long sequence of asynchronous operations that all actually completed synchronously: if the callbacks handled all of the work, then each callback would initiate the next operation, whose callback would initiate the next operation, but because they were completing synchronously, their callbacks would also be invoked synchronously as part of theBeginXxmethods, so each call would get deeper and deeper on the stack, until it potentially overflowed):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 }Note that both the caller and the callback use the same
CompletedSynchronouslyproperty to determine which of them runs the callback. As such,CompletedSynchronouslymust always return the same value for this particular instance. If it doesn’t, erroneous behavior can easily result. For example, their implementation hasCompletedSynchronouslyreturning the equivalent ofIsCompleted. So imagine the following sequence of events:
BeginXxis called and initiates the async operationBeginXxreturns to its caller, which checksCompletedSynchronously, which is false, because the operation hasn’t completed yet.- Now the operation completes and the callback is invoked. The callback sees that
CompletedSynchronouslyis true, and so doesn’t do any of the subsequent work, because it assumes the caller did it.- And now no one’s run or will run the callback.
In short, the library has a bug. If you changed
CompletedSynchronouslyto return true, you masked this problem, but you likely caused another: if the caller (in your case,FromAsync) thinks that the operation has already completed, it’ll proceed to immediately call theEndXxmethod, which will block until the async operation has completed, so you’ve turned your asynchronous operations into synchronous ones. Have you tried just always returning false fromCompletedSynchronouslyinstead of always returning true?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With