Безопасный с точки зрения типов асинхронный вызов делегата "выпустил-забыл" в C#

Мне недавно был нужен безопасный с точки зрения типов механизм "выпустил-забыл" для выполнения кода асинхронно.

Идеально, то, что я хотел бы сделать, является чем-то как:

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

К сожалению, очевидный выбор вызова BeginInvoke() без соответствия EndInvoke() не работает - это приводит к медленной утечке ресурсов (так как состояние asyn сохранено временем выполнения и никогда не выпускается..., это ожидает возможный вызов к EndInvoke(). Я также не могу выполнить код пула потоков.NET, потому что может потребоваться очень долгое время для завершения (рекомендуется только выполнить относительно недолгий код пула потоков) - это лишает возможности использовать ThreadPool.QueueUserWorkItem().

Первоначально, мне только было нужно это поведение для методов, подпись которых соответствует Action, Action<...>, или 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<>

}
13
задан LBushkin 6 May 2010 в 23:25
поделиться

5 ответов

Вы можете передать EndInvoke как AsyncCallback для BeginInvoke:

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

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

Это поможет?

8
ответ дан 1 December 2019 в 22:22
поделиться

Я заметил, что никто не ответил на это:

Я также не могу запустить код на. NET, потому что это может занять очень много времени (рекомендуется запускать только относительно недолговечный код в пуле потоков) - это делает невозможным использование ThreadPool.QueueUserWorkItem ().

Я не уверен, знаете ли вы об этом, но делегаты async на самом деле делают именно это - они ставят работу в очередь рабочего потока в ThreadPool , точно так же, как если бы вы QueueUserWorkItem .

Единственный случай, когда асинхронные делегаты ведут себя по-другому, - это специальные делегаты фреймворка, такие как Stream.BeginRead или Socket.BeginSend . Вместо этого они используют порты завершения ввода-вывода.

Если вы не выполняете сотни таких задач в среде ASP.NET, я бы рекомендовал просто использовать пул потоков.

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

Или, в .NET 4, вы можете использовать фабрику задач:

Task.Factory.StartNew(action);

(обратите внимание, что в приведенном выше примере также будет использоваться пул потоков!)

5
ответ дан 1 December 2019 в 22:22
поделиться

Созданный компилятором метод BeginInvoke также вызывается в пуле потоков ( ссылка ). Так что я думаю, что ThreadPool.QueueUserWorkItem будет в порядке, за исключением того, что вы немного более четко об этом говорите (и я полагаю, что в будущем CLR может выбрать запуск методов BeginInvoke 'ed на ) другой пул потоков).

4
ответ дан 1 December 2019 в 22:22
поделиться

Этот умный парень Скит обращается к этой теме здесь.

Примерно на полпути ниже есть другой подход к "выстрелить и забыть".

0
ответ дан 1 December 2019 в 22:22
поделиться

Как насчет чего-то вроде:

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.

5
ответ дан 1 December 2019 в 22:22
поделиться
Другие вопросы по тегам:

Похожие вопросы: