Asynchronous begin/end in same method

2019-01-29 11:28发布

(I'm using .Net 4.0)

I want to call a WCF service asynchronously, from my service layer. This service layer is used by and MVC.Net controller. I've read that it's good practice to call a WCF service asynchronously. So I'm using begin/end (apm). I want to double check if I'm doing it richt:

public byte[] GetSomeData()
{
    IAsyncResult result = myServiceClient.BeginDoSomething(someInputValue, null, null);

    var data = _pdfCreatieService.EndCreateForPreview(result);

    return data;
}

I'm not totally sure about the code above, because I've seen constructions like the code below, which seem a bit more complex and unnecessary in my case:

public byte[] GetSomeData()
{
    var myState = new MyState();

    IAsyncResult result = _myServiceClient.BeginDoSomething(someInputValue, CreateForPreviewCallback, myState);

    result.AsyncWaitHandle.WaitOne();

    return myState.Bytes;
}

private void DoSomethingCallback(IAsyncResult result)
{
    var myState = (MyState)result.AsyncState;

    myState.Bytes = _myServiceClient.EndDoSomething(result);
}

Thanks Avner Shahar-Kashtan, Ned Stoyanov and Noseratio. Your answers are really insightfull!

3条回答
Anthone
2楼-- · 2019-01-29 12:12

Both of your approaches are doing what is called sync over async. That is executing an asynchronous method in synchronous fashion. A better approach would be to build your own asynchronous method to rerieve the data with TaskCompletionSource. I haven't tested this, but you should be able to do something like this:

public Task<byte[]> GetSomeDataAsync()
{
    var tcs = new TaskCompletionSource();

    IAsyncResult result = myServiceClient.BeginDoSomething(someInputValue, x => 
    {
        try
        {
            var data = _pdfCreatieService.EndCreateForPreview(result);
            tcs.SetResult(data);
        }
        catch(Exception ex)
        {
            tcs.SetException(ex);
        }
    }, null);

    return tcs.Task;
}

Then to use it simply do this

GetSomeDataAsync().ContinueWith(t => /*<Use retrieved data from t.Result>*/);

This code will return straight away and once the asynchronous operation is complete the ContinueWith part will execute.

查看更多
三岁会撩人
3楼-- · 2019-01-29 12:13

In ASP.NET MVC, you only benefit from asynchronous calls if your controller is asynchronous too. Apparently, this is not the case with your code, because you're blocking with WaitOne inside your controller's method.

Implementing asynchronous controllers is really easy with .NET 4.5, check "Using Asynchronous Methods in ASP.NET MVC 4" for more info.

With .NET 4.0, it's a bit more tedious, check "Using an Asynchronous Controller in ASP.NET MVC". Your controller should derive from AsyncController and use AsyncManager to notify the ASP.NET stack about the pending asynchronous operations.

Here's an example from there for .NET 4. 0, adapted for your case(untested). Note the use of Task.Factory.FromAsync and Task.ContinueWith:

public static class WcfExt
{
    public static Task<byte[]> DoSomethingAsync(
        this IMyService service,
        string someInputValue)
    {
        return Task.Factory.FromAsync(
             (asyncCallback, asyncState) =>
                 service.BeginDoSomething(someInputValue, asyncCallback, asyncState),
             (asyncResult) =>
                 service.EndDoSomething(asyncResult);
    }
}

public class PortalController : AsyncController 
{
    public void NewsAsync(string someInputValue) {

        AsyncManager.OutstandingOperations.Increment();

        var myService = new MyService();
        myService.DoSomethingAsync(someInputValue).ContinueWith((task) =>
        {
            AsyncManager.Parameters["data"] = task.Result;
            AsyncManager.OutstandingOperations.Decrement();
        }, TaskScheduler.FromCurrentSynchronizationContext());
    }

    public ActionResult NewsCompleted(byte[] data) 
    {
        return View("News", new ViewStringModel
        {
            NewsData = data
        });
    }
}
查看更多
淡お忘
4楼-- · 2019-01-29 12:19

What your code will do is, in effect, take an asynchronous method and call it synchronously. When you call the EndDoSomething method, you are effectively blocking your thread until the asynchronous method has completed, which is exactly the opposite of an async call.

Of course, your second code block also calls the code synchronously, by blocking executing explicitly, using the waithandle.

What you want to do is, instead of returning the byte[] from your initial method, have your DoSomethingCallback do something active with the bytes - either store them in some class member that can be checked by the controller, raise an event, or do something else. If you're waiting for your async call, you're not getting any benefit.

What you can also do if you're using .NET 4.5 or higher (or .NET 4.0 in VS2012, using the BCL Async Package) is to use async/await, which is a nice wrapper which will allow you to consolidate the calling method and the callback method into a single, more coherent method.

But regardless of the syntax or libraries you choose, your first step is to understand that async programming necessarily breaks your code's control flow into the invocation and the result callback, or continuation of the asynchronous operation.

查看更多
登录 后发表回答