locked
Will "Add Service Reference" be updated to generate async functions instead of BeginX/EndX

    Question

  • Currently  Add Service Reference generates the BeginX/EndX pattern.  We can use  Task.Factory.FromAsync  to convert that to tasks. But Task.Factory.FromAsync only has overloads for up to 3 arguments , so for services that take more arguments we have to roll our own.

    Are there plans to update Add Service Reference to generate the new async functions?

    Sunday, October 31, 2010 6:45 PM

Answers

  • Hi Bankoh-

    Yes. 

    In fact, in the Async CTP samples contains an extension to the Add Service Reference dialog that provides just that (though not for Silverlight in the CTP).  Look at:
        \Samples\(C# WCF) Stock Quotes\TaskWsdlImportExtension

    If you add a reference to that project from your client project, and then augment your app.config with a metadata section as in the following:

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
     <system.serviceModel>
      <client>
       <metadata>
        <wsdlImporters>
         <extension type="TaskWsdlImportExtension.TaskAsyncWsdlImportExtension, TaskWsdlImportExtension" />
        </wsdlImporters>
       </metadata>
      </client>
     </system.serviceModel>
    </configuration>
    

    when you then Add Service Reference and choose to generate async methods, the generator will then spit out Task-returning methods in addition to Begin/End methods.

    re: "Task.Factory.FromAsync only has overloads for up to 3 arguments "

    While you can roll your own for efficiency reasons, you don't have to.  Task.Factory.FromAsync also includes overloads that take an IAsyncResult as the first parameter, and those overloads allow you to use Begin/End methods that take any number of parameter to the Begin method, e.g.

    var t = Task.Factory.FromAsync(source.BeginXx(p1, p2, p3, p4, p5), source.EndXx, null);

    Rather than passing a delegate to the BeginXx method, you simply invoke the BeginXx method and pass in the resulting IAsyncResult.

    I hope that helps.

    Sunday, October 31, 2010 8:00 PM

All replies

  • Hi Bankoh-

    Yes. 

    In fact, in the Async CTP samples contains an extension to the Add Service Reference dialog that provides just that (though not for Silverlight in the CTP).  Look at:
        \Samples\(C# WCF) Stock Quotes\TaskWsdlImportExtension

    If you add a reference to that project from your client project, and then augment your app.config with a metadata section as in the following:

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
     <system.serviceModel>
      <client>
       <metadata>
        <wsdlImporters>
         <extension type="TaskWsdlImportExtension.TaskAsyncWsdlImportExtension, TaskWsdlImportExtension" />
        </wsdlImporters>
       </metadata>
      </client>
     </system.serviceModel>
    </configuration>
    

    when you then Add Service Reference and choose to generate async methods, the generator will then spit out Task-returning methods in addition to Begin/End methods.

    re: "Task.Factory.FromAsync only has overloads for up to 3 arguments "

    While you can roll your own for efficiency reasons, you don't have to.  Task.Factory.FromAsync also includes overloads that take an IAsyncResult as the first parameter, and those overloads allow you to use Begin/End methods that take any number of parameter to the Begin method, e.g.

    var t = Task.Factory.FromAsync(source.BeginXx(p1, p2, p3, p4, p5), source.EndXx, null);

    Rather than passing a delegate to the BeginXx method, you simply invoke the BeginXx method and pass in the resulting IAsyncResult.

    I hope that helps.

    Sunday, October 31, 2010 8:00 PM
  • Hi Steve,

    in this code:  var t = Task.Factory.FromAsync(source.BeginXx(p1, p2, p3, p4, p5), source.EndXx, null);

    what do you pass as the AsyncCallback to the BeginXx method that has e.g. 5 arguments ?

    BeginXx(p1, p2, p3, p4, p5, asyncCallback, objectState)

     

    Theo

     

     

     

    Thursday, January 06, 2011 4:08 AM
  • Hi Theo-

    Ah, sorry, that should have been BeginXx(p1, p2, p3, p4, p5, null, null).  This overload of Task.Factory.FromAsync works under the covers by using the ThreadPool's RegisterWaitForSingleObject to monitor for the operation's completion using the AsyncWaitHandle of the IAsyncResult returned from BeginXx; as a result, it doesn't use the AsyncCallback to be notified of completion, and thus it can and should be null in this case.  As mentioned previously, this use of RegisterWaitForSingleObject isn't as efficient as using the callback, but it does prevent you from having to write all of the boilerplate to write your own >3 parameter version of FromAsync.

    Thursday, January 06, 2011 11:17 PM
  • Hey Steve,

    thanks for your prompt reply. How do cancellation and time-outs fit in the whole FromAsync picture ?

    At this simple example that follows:

    public async Task<IEnumerable<double>> CallMyServiceAsync(....., CancellationToken cancellationToken)
    {
     IEnumerable<double> result = Task.Factory.FromAsync<IEnumerable<double>>(
      BeginXx(.....), EndXx
     );
    
     cancellationToken.ThrowIfCancellationRequested();
     return result.ToList();
    }
    
    

    Suppose that it's a service that requires from a remote server to perform a long compulation (lasts e.g. 10 seconds) and return the result for display on the client screen. When I signal the token at e.g. t = 2 seconds , the CallMyService method doesn't return immediately with an OperationCancelled error; instead it waits for the entire 10 seconds.

    That means that the cancellation is executed just before the callback. While the thread is in the waiting state there is no way to recover control of that thread. From what I understand, RegisterWaitForSingleObject assigns a thead from the pool to do the waiting, thus when signaled with UnregisterWait to cancel the wait operation, it should return immediately. The same when a timeout occurs.

    What is the proper way to implement cancellation and time-outs in this case?

    Theo

     

    Monday, January 10, 2011 2:46 AM
  • Hi Theo-

    It really depends on the capabilities of the underlying API you're using.  I

    If the API does support cancellation/timeout, then you just need to hook up the CancellationToken to whatever the underlying API's mechanism is.  For example, if the type on which the Begin/End methods exist exposes a Cancel method or an Abort method or something similar, then you can use the cancellationToken.Register method to supply a delegate that will call the Cancel/Abort method when cancellation is requested.

    If the Begin/End API you're using has no built-in support for cancellation or timeout, then you really have two choices:
    (1) Don't support cancellation or timeout in your wrapper.
    (2) Support cancellation/timeout by completing the returned task but allowing the underlying operation to continue.

    For case #1, you don't have to do anything, but of course that's the problem you're currently facing.  For #2, you have a few options.  For example, let's say I wanted to implement timeout functionality.  I could do that with my own WithTimeout extension method that I can apply to any task, getting back a new task which will have the results of the original if it completes within the specified time limit, or being faulted with a timeout exception if it did not complete within the specified time, e.g. something like the following:

    public static async Task<TResult> WithTimeout(this Task<TResult> task, TimeSpan timeout)
    {
        if (task == await TaskEx.WhenAny(task, TaskEx.Delay(timeout))) return await task;
        else throw new TimeoutException();
    }

    (You could of course also build this method without using await.)

    Now, if you were previously awaiting a task as in "await t;", you can instead use "await t.WithTimeout(someTimeout);" in order to wait for at most someTimeout.

    The same kind of thing can be done for cancellation as well.  You can choose to cancel the returned task if a cancellation request comes in before the operation completes.

    Just keep in mind that with these approaches, because the underlying APIs you're using doesn't support cancellation, the underlying work you've kicked off will still be on-going and consuming resources... this mechanism I've discussed above is just a veneer that allows you to stop waiting for the operation to complete.  If you want to truly be able to cancel the underlying work, you should work with the provider of the API you're relying on to make it cancelable in some fashion.

    I hope that helps.

    Monday, January 10, 2011 2:58 AM
  • I've submitted a bug on Microsoft Connect.
    Thursday, March 29, 2012 10:46 AM
  • I agree that FromAsync should be given more overloads (I ran into this while taskifying the Azure SDK). Hopefully Microsoft will add them in (I haven't checked the .NET 4.5 Beta yet - they could very well be there already).

    In the meantime, you can use Nito.AsyncEx.TaskFactory[<TResult>].FromApm which uses T4 to generate 14-argument versions (and adding more would be a 2-line change).

           -Steve


    Programming blog: http://nitoprograms.blogspot.com/
      Including my TCP/IP .NET Sockets FAQ
      and How to Implement IDisposable and Finalizers: 3 Easy Rules
    Microsoft Certified Professional Developer

    How to get to Heaven according to the Bible

    Thursday, March 29, 2012 11:49 AM
  • Note that you can use the existing overloads even with more parameters than 3, just in a slightly different way. 

    For example, consider a method like UdpClient.SendAsync:

    public IAsyncResult BeginSend(byte[] datagram, int bytes, string hostname, int port, AsyncCallback requestCallback, object state);

    It uses 4 parameters in addition to the standard AsyncCallback and object state parameters, but I can still use FromAsync as so:

    UdpClient client = ...;
    Task<int> sendOp = Task<int>.Factory.FromAsync(
        (callback, state) => client.BeginSend(datagram, bytes, hostname, port, callback, state),
        client.EndSend, null);


    Thursday, March 29, 2012 4:13 PM
  • Stephen, I use the method from Theo's answer:

    Task.Factory.FromAsync(
        client.BeginMethod(arg1, arg2, arg3, arg4),
        client.EndMethod,
        null // state
    ).ContinueWith(...);
    works fine for me.
    • Edited by abatishchev Thursday, March 29, 2012 7:05 PM
    Thursday, March 29, 2012 7:03 PM
  • Stephen, don't you know,

    does passing IAsyncResult itself, or just delegate have any differences?
    I want to make sure that I execute (wcf async proxy) as a task from thread pool, not on current (web app client request) thread.

    Am I correct using ContinueWith() to handle an exception could occur? How is it better to perform that?

    Thanks!

    Friday, March 30, 2012 2:48 PM