在C#中异步调用同步服务调用的策略

chi*_*tom 7 c# service synchronization asynchronous

业务逻辑封装在同步服务调用之后,例如:

interface IFooService
{
    Foo GetFooById(int id);
    int SaveFoo(Foo foo);
}
Run Code Online (Sandbox Code Playgroud)

异步方式扩展/使用这些服务调用的最佳方法是什么?

目前我已经创建了一个简单的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);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这让我可以异步调用任何东西:

AsyncUtils(
     () => _fooService.SaveFoo(foo),
     id => HandleFooSavedSuccessfully(id),
     ex => HandleFooSaveError(ex));
Run Code Online (Sandbox Code Playgroud)

虽然这适用于简单的用例,但如果其他进程需要协调结果,它会很快变得棘手,例如,如果我需要在当前线程可以继续之前异步保存三个对象,那么我想要一种等待/加入的方法工人线程.

我到目前为止所考虑的选项包括:

  • 让AsyncUtils返回一个WaitHandle
  • 让AsyncUtils使用AsyncMethodCaller并返回IAsyncResult
  • 重写API以包括Begin,End异步调用

例如:类似的东西:

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);
}
Run Code Online (Sandbox Code Playgroud)

我还应该考虑其他方法吗?每个的好处和潜在缺陷是什么?

理想情况下,我希望保持服务层简单/同步,并提供一些易于使用的实用程序方法来异步调用它们.我有兴趣听听适用于C#3.5和C#4的解决方案和想法(我们尚未升级,但将在不久的将来完成).

期待您的想法.

Ree*_*sey 3

鉴于您要求仅保留 .NET 2.0,而不在 3.5 或 4.0 上工作,这可能是最好的选择。

我对你们目前的实施有三点评论。

  1. 您使用ThreadPool.UnsafeQueueUserWorkItem有具体原因吗?除非有特殊原因需要这样做,否则我建议改用ThreadPool.QueueUserWorkItem,特别是如果您在大型开发团队中。当您丢失调用堆栈时,不安全版本可能会出现安全缺陷,从而导致无法严格控制权限。

  2. 当前的异常处理设计(使用 )failureCallback将吞掉所有异常,并且不提供任何反馈,除非定义了回调。如果您不打算正确处理异常,最好传播异常并让它冒泡。或者,您可以以某种方式将其推回调用线程,尽管这需要使用更像IAsyncResult.

  3. 您当前无法判断异步调用是否完成。这将是在设计中使用IAsyncResult的另一个优点(尽管它确实增加了实现的复杂性)。


但是,一旦升级到 .NET 4,我建议将其放入 TaskTask<T>,因为它旨在非常干净地处理此问题。代替:

AsyncUtils(
     () => _fooService.SaveFoo(foo),
     id => HandleFooSavedSuccessfully(id),
     ex => HandleFooSaveError(ex));
Run Code Online (Sandbox Code Playgroud)

您可以使用内置工具并只需编写:

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 );
Run Code Online (Sandbox Code Playgroud)

当然,最后一行有点奇怪,但这主要是因为我试图保留您现有的 API。如果你稍微修改一下,你可以简化它......