Typesafe вызов вызова асинхронного делегата fire-and-забудьте в C # - PullRequest
13 голосов
/ 07 мая 2010

Недавно я обнаружил, что мне нужен безопасный для безопасности механизм «запускай и забывай» для асинхронного выполнения кода.

В идеале я хотел бы сделать что-то вроде:

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

К сожалению, очевидный выбор вызова BeginInvoke() без соответствующего EndInvoke() не работает - это приводит к медленной утечке ресурсов (поскольку асинхронное состояние сохраняется во время выполнения и никогда не освобождается ... оно ожидает возможного вызов 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<>

}

Ответы [ 7 ]

8 голосов
/ 07 мая 2010

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

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

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

Это помогает?

5 голосов
/ 07 мая 2010

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

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

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

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

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

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

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

Task.Factory.StartNew(action);

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

5 голосов
/ 07 мая 2010

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

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);

Чтобы добавить безопасность типов, просто разверните вспомогательные методы. Т4, вероятно, лучше здесь.

4 голосов
/ 07 мая 2010

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

0 голосов
/ 29 июля 2014

Дайте этому методу расширения шанс (на C # Является ли action.BeginInvoke (action.EndInvoke, null) хорошей идеей? ), чтобы гарантировать отсутствие утечек памяти:

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

И вы можете использовать его с такими общими параметрами, как:

T1 param1 = someValue;
T2 param2 = otherValue;
(() => myFunc<T1,T2>(param1,param2)).FireAndForget();
0 голосов
/ 06 ноября 2010

Вы можете написать собственную реализацию пула потоков. Вероятно, это звучит как занятое дело, чем на самом деле. Тогда вам не нужно следовать совету «запускайте только относительно короткий код».

0 голосов
/ 07 мая 2010

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...