C#中的Typesafe即发即弃异步委托调用

时间:2010-05-06 23:25:56

标签: c# generics asynchronous delegates .net-4.0

我最近发现自己需要一种类型安全的“即发即忘”机制来异步运行代码。

理想情况下,我想要做的是:

var myAction = (Action)(() => Console.WriteLine("yada yada"));
myAction.FireAndForget(); // async invocation

不幸的是,在没有相应的BeginInvoke()的情况下调用EndInvoke()的明显选择不起作用 - 它导致资源泄漏缓慢(因为asyn状态由运行时保持并且从未发布...它期望最终调用EndInvoke()。我也无法在.NET线程池上运行代码,因为它可能需要很长时间才能完成(建议只在线程上运行相对短暂的代码池) - 这使得无法使用ThreadPool.QueueUserWorkItem()

最初,我只对签名与ActionAction<...>Func<...>匹配的方法才需要此行为。所以我把一组扩展方法(见下面的清单)放在一起,让我在不遇到资源泄漏的情况下这样做。每个版本的Action / Func都有重载。

不幸的是,我现在想将此代码移植到.NET 4,其中Action和Func上的泛型参数的数量已大幅增加。在我编写T4脚本来生成这些之前,我还希望找到一种更简单,更优雅的方法来实现这一点。欢迎任何想法。

public static class AsyncExt
{
    public static void FireAndForget( this Action action )
    {
        action.BeginInvoke(OnActionCompleted, action);
    }

    public static void FireAndForget<T1>( this Action<T1> action, T1 arg1 )
    {
        action.BeginInvoke(arg1, OnActionCompleted<T1>, action);
    }

    public static void FireAndForget<T1,T2>( this Action<T1,T2> action, T1 arg1, T2 arg2 )
    {
        action.BeginInvoke(arg1, arg2, OnActionCompleted<T1, T2>, action);
    }

    public static void FireAndForget<TResult>(this Func<TResult> func, TResult arg1)
    {
        func.BeginInvoke(OnFuncCompleted<TResult>, func);
    }

    public static void FireAndForget<T1,TResult>(this Func<T1, TResult> action, T1 arg1)
    {
        action.BeginInvoke(arg1, OnFuncCompleted<T1,TResult>, action);
    }

    // more overloads of FireAndForget<..>() for Action<..> and Func<..>

    private static void OnActionCompleted( IAsyncResult result )
    {
        var action = (Action)result.AsyncState;
        action.EndInvoke(result);
    }

    private static void OnActionCompleted<T1>( IAsyncResult result )
    {
        var action = (Action<T1>)result.AsyncState;
        action.EndInvoke( result );
    }

    private static void OnActionCompleted<T1,T2>(IAsyncResult result)
    {
        var action = (Action<T1,T2>)result.AsyncState;
        action.EndInvoke(result);
    }

    private static void OnFuncCompleted<TResult>( IAsyncResult result )
    {
        var func = (Func<TResult>)result.AsyncState;
        func.EndInvoke( result );
    }

    private static void OnFuncCompleted<T1,TResult>(IAsyncResult result)
    {
        var func = (Func<T1, TResult>)result.AsyncState;
        func.EndInvoke(result);
    }

    // more overloads of OnActionCompleted<> and OnFuncCompleted<>

}

7 个答案:

答案 0 :(得分:8)

您可以将EndInvoke作为AsyncCallback传递给BeginInvoke:

Action<byte[], int, int> action = // ...

action.BeginInvoke(buffer, 0, buffer.Length, action.EndInvoke, null);

这有帮助吗?

答案 1 :(得分:5)

如下:

public static class FireAndForgetMethods
{
    public static void FireAndForget<T>(this Action<T> act,T arg1)
    {
        var tsk = Task.Factory.StartNew( ()=> act(arg1),
                                         TaskCreationOptions.LongRunning);
    }
}

使用它像:

Action<int> foo = (t) => { Thread.Sleep(t); };
foo.FireAndForget(100);

要添加类型安全性,只需展开辅助方法即可。 T4在这里可能是最好的。

答案 2 :(得分:5)

我注意到没有人对此作出回应:

  

我也无法在.NET线程池上运行代码,因为它可能需要很长时间才能完成(建议只在线程池上运行相对短暂的代码) - 这使得它无法使用ThreadPool.QueueUserWorkItem()。

我不确定你是否意识到这一点,但是异步委托实际上正是这样做的 - 他们将工作排在ThreadPool的工作线程上,与你{{1}完全相同}}

异步委托表现不同的唯一情况是,它们是QueueUserWorkItemStream.BeginRead等特殊框架代理。这些使用I / O完成端口。

除非你在ASP.NET环境中使用数百个这样的任务,否则我建议你只使用线程池。

Socket.BeginSend

或者,在.NET 4中,您可以使用任务工厂:

ThreadPool.QueueUserWorkItem(s => action());

(注意以上也会使用线程池!)

答案 3 :(得分:4)

还在线程池(reference)上调用编译器生成的BeginInvoke方法。所以我认为ThreadPool.QueueUserWorkItem会没问题,除非你对它有点明确(我猜想未来的CLR可以选择在不同<上运行BeginInvoke'ed方法/ em>线程池)。

答案 4 :(得分:0)

聪明的小姐Skeet接近这个主题here

有一种不同的方法可以“解雇”,大约一半。

答案 5 :(得分:0)

您可以编写自己的线程池实现。这可能听起来像是工作而不是实际。那么你就不必遵守“只运行相对短命的代码”的建议。

答案 6 :(得分:0)

为此扩展方法提供一个镜头(每C# Is action.BeginInvoke(action.EndInvoke,null) a good idea?)以确保没有内存泄漏:

public static void FireAndForget( this Action action )
{
    action.BeginInvoke(action.EndInvoke, null);
}

您可以将它与通用参数一起使用:

T1 param1 = someValue;
T2 param2 = otherValue;
(() => myFunc<T1,T2>(param1,param2)).FireAndForget();