(我正在使用.Net 4.0)
我想从服务层异步调用WCF服务。此服务层由MVC.Net控制器使用。我已经读过,异步调用WCF服务是一种很好的做法。所以我正在使用begin / end(apm)。我想仔细检查一下我是否做得很丰富:
public byte[] GetSomeData()
{
IAsyncResult result = myServiceClient.BeginDoSomething(someInputValue, null, null);
var data = _pdfCreatieService.EndCreateForPreview(result);
return data;
}
我不完全确定上面的代码,因为我已经看到了类似下面代码的构造,在我的情况下看起来有点复杂和不必要:
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);
}
感谢Avner Shahar-Kashtan,Ned Stoyanov和Noseratio。你的答案非常有见地!
答案 0 :(得分:5)
您的代码实际上将采用异步方法并将其称为同步。当您调用EndDoSomething
方法时,您实际上是在阻塞线程,直到异步方法完成,这与异步调用完全相反。
当然,你的第二个代码块也通过使用waithandle显式阻止执行来同步调用代码。
您要做的是,不是从初始方法返回byte[]
,而是让DoSomethingCallback
对字节执行某些操作 - 将它们存储在可以通过其检查的某个类成员中控制器,举起事件或做其他事情。如果您正在等待异步呼叫,则无法获得任何好处。
如果您使用的是.NET 4.5或更高版本(或VS2012中的.NET 4.0,使用BCL Async Package),您还可以使用async/await
,这是一个很好的包装器允许您将调用方法和回调方法合并为一个更连贯的方法。
但无论您选择哪种语法或库,您的第一步是了解异步编程必然会将代码的控制流分解为异步操作的调用和结果回调或 continuation 。
答案 1 :(得分:2)
在ASP.NET MVC中,如果您的控制器也是异步的,您只能从异步调用中受益。显然,您的代码不是这种情况,因为您在控制器的方法中阻止了WaitOne
。
使用.NET 4.5实现异步控制器非常简单,请查看"Using Asynchronous Methods in ASP.NET MVC 4"以获取更多信息。
使用.NET 4.0,它有点繁琐,请检查"Using an Asynchronous Controller in ASP.NET MVC"。您的控制器应该派生自AsyncController
并使用AsyncManager
来通知ASP.NET堆栈有关挂起的异步操作。
以下是.NET 4.0的示例,适用于您的情况(未经测试)。请注意使用Task.Factory.FromAsync
和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
});
}
}
答案 2 :(得分:1)
您的两种方法都在进行所谓的同步异步。那就是以同步方式执行异步方法。更好的方法是构建自己的异步方法以使用TaskCompletionSource
来恢复数据。我没有对此进行过测试,但您应该能够做到这样的事情:
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;
}
然后使用它只需执行此操作
GetSomeDataAsync().ContinueWith(t => /*<Use retrieved data from t.Result>*/);
此代码将立即返回,异步操作完成后,ContinueWith
部分将执行。