将业务逻辑封装在同步服务调用之后,例如:
interface IFooService
{
Foo GetFooById(int id);
int SaveFoo(Foo foo);
}
以异步方式扩展/使用这些服务调用的最佳方式是什么?
目前我已经创建了一个简单的AsyncUtils类:
public static class AsyncUtils
{
public static void Execute<T>(Func<T> asyncFunc)
{
Execute(asyncFunc, null, null);
}
public static void Execute<T>(Func<T> asyncFunc, Action<T> successCallback)
{
Execute(asyncFunc, successCallback, null);
}
public static void Execute<T>(Func<T> asyncFunc, Action<T> successCallback, Action<Exception> failureCallback)
{
ThreadPool.UnsafeQueueUserWorkItem(state => ExecuteAndHandleError(asyncFunc, successCallback, failureCallback), null);
}
private static void ExecuteAndHandleError<T>(Func<T> asyncFunc, Action<T> successCallback, Action<Exception> failureCallback)
{
try
{
T result = asyncFunc();
if (successCallback != null)
{
successCallback(result);
}
}
catch (Exception e)
{
if (failureCallback != null)
{
failureCallback(e);
}
}
}
}
让我可以异步调用任何内容:
AsyncUtils(
() => _fooService.SaveFoo(foo),
id => HandleFooSavedSuccessfully(id),
ex => HandleFooSaveError(ex));
虽然这适用于简单的用例,但如果其他进程需要协调结果,很快就会变得棘手,例如,如果我需要在当前线程可以继续之前异步保存三个对象,那么我想要一种等待的方法 - on / join工作线程。
到目前为止我想到的选项包括:
e.g。类似的东西:
interface IFooService
{
Foo GetFooById(int id);
IAsyncResult BeginGetFooById(int id);
Foo EndGetFooById(IAsyncResult result);
int SaveFoo(Foo foo);
IAsyncResult BeginSaveFoo(Foo foo);
int EndSaveFoo(IAsyncResult result);
}
我还应该考虑其他方法吗?每种方法的好处和潜在缺陷是什么?
理想情况下,我希望保持服务层简单/同步,并提供一些易于使用的实用程序方法来异步调用它们。我有兴趣了解适用于C#3.5和C#4的解决方案和想法(我们尚未升级,但将在不久的将来完成)。
期待您的想法。
答案 0 :(得分:3)
鉴于您需要仅保留.NET 2.0,而不能使用3.5或4.0,这可能是最佳选择。
我对你当前的实施有三点评论。
您使用ThreadPool.UnsafeQueueUserWorkItem的具体原因是什么?除非有特殊原因需要这样做,否则我建议使用ThreadPool.QueueUserWorkItem,尤其是如果您在大型开发团队中。当您丢失调用堆栈时,不安全版本可能会出现安全漏洞,因此,能够严密控制权限。
使用failureCallback
进行异常处理的当前设计将吞下所有异常,并且不提供任何反馈,除非定义了回调。传播异常可能会更好,如果你不打算正确处理它就让它冒泡。或者,您可以以某种方式将其推回调用线程,但这需要使用更像IAsyncResult
的内容。
您目前无法判断异步调用是否已完成。这将是在您的设计中使用IAsyncResult的另一个好处(尽管它确实增加了实现的复杂性)。
然而,一旦升级到.NET 4,我建议将其放在Task或Task<T>
中,因为它的设计非常干净。而不是:
AsyncUtils(
() => _fooService.SaveFoo(foo),
id => HandleFooSavedSuccessfully(id),
ex => HandleFooSaveError(ex));
您可以使用内置工具,只需编写:
var task = Task.Factory.StartNew(
() => return _fooService.SaveFoo(foo) );
task.ContinueWith(
t => HandleFooSavedSuccessfully(t.Result),
TaskContinuationOptions.NotOnFaulted);
task.ContinueWith(
t => try { t.Wait(); } catch( Exception e) { HandleFooSaveError(e); },
TaskContinuationOptions.OnlyOnFaulted );
当然,最后一行有点奇怪,但这主要是因为我试图保留你现有的API。如果你稍微改写它,你可以简化它......
答案 1 :(得分:2)
异步接口(基于IAsyncResult
)仅在封面下有一些非阻塞调用时才有用。接口的要点是可以在不阻塞调用程序线程的情况下进行调用。
这在您可以进行某些系统调用的情况下非常有用,系统会在发生某些事情时通知您(例如,当收到HTTP响应或发生事件时)。
使用基于IAsyncResult
的界面的代价是你必须以一种有点笨拙的方式编写代码(通过使用回调进行每次调用)。更糟糕的是,异步API使得无法使用标准语言结构,如while
,for
或try
.. catch
。
我没有真正意识到将同步 API包装到异步接口的重点,因为你不会得到好处(总会有一些线程被阻止)而且你只会采用更尴尬的方式来调用它。
当然,以某种方式在后台线程上运行同步代码是非常有意义的(以避免阻塞主应用程序线程)。在.NET 4.0上使用Task<T>
或在.NET 2.0上使用QueueUserWorkItem
。但是,我不确定这是否应该在服务中自动完成 - 感觉就像在调用者端这样做会更容易,因为您可能需要对服务执行多次调用。使用异步API,您必须编写如下内容:
svc.BeginGetFooId(ar1 => {
var foo = ar1.Result;
foo.Prop = 123;
svc.BeginSaveFoo(foo, ar2 => {
// etc...
}
});
使用同步API时,您可以编写如下内容:
ThreadPool.QueueUserWorkItem(() => {
var foo = svc.GetFooId();
foo.Prop = 123;
svc.SaveFoo(foo);
});
答案 2 :(得分:1)
以下是对里德后续问题的回应。我并不是说这是正确的方式。
public static int PerformSlowly(int id)
{
// Addition isn't so hard, but let's pretend.
Thread.Sleep(10000);
return 42 + id;
}
public static Task<int> PerformTask(int id)
{
// Here's the straightforward approach.
return Task.Factory.StartNew(() => PerformSlowly(id));
}
public static Lazy<int> PerformLazily(int id)
{
// Start performing it now, but don't block.
var task = PerformTask(id);
// JIT for the value being checked, block and retrieve.
return new Lazy<int>(() => task.Result);
}
static void Main(string[] args)
{
int i;
// Start calculating the result, using a Lazy<int> as the future value.
var result = PerformLazily(7);
// Do assorted work, then get result.
i = result.Value;
// The alternative is to use the Task as the future value.
var task = PerformTask(7);
// Do assorted work, then get result.
i = task.Result;
}