Ответ 1
Вы можете передать EndInvoke как AsyncCallback для BeginInvoke:
Action<byte[], int, int> action = // ...
action.BeginInvoke(buffer, 0, buffer.Length, action.EndInvoke, null);
Помогает ли это?
Недавно я обнаружил, что мне нужен типичный механизм "fire-and-forget" для асинхронного запуска кода.
В идеале, я хотел бы сделать что-то вроде:
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 script, чтобы сгенерировать их, я также надеялся найти более простой и элегантный способ сделать это. Любые идеи приветствуются.
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<>
}
Вы можете передать EndInvoke как AsyncCallback для BeginInvoke:
Action<byte[], int, int> action = // ...
action.BeginInvoke(buffer, 0, buffer.Length, action.EndInvoke, null);
Помогает ли это?
Как насчет чего-то типа:
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, вероятно, лучше всего здесь.
Я заметил, что никто не ответил на это:
Я также не могу запустить код в пуле потоков .NET, потому что это может занять очень много времени (он рекомендует использовать относительно короткий код в пуле потоков) - это делает невозможным использование ThreadPool.QueueUserWorkItem().
Я не уверен, знаете ли вы об этом, но на самом деле асинхронные делегаты делают именно это: они ставят очередь на рабочий поток в ThreadPool
, точно так же, как если бы вы делали QueueUserWorkItem
.
Единственный раз, когда асинхронные делегаты ведут себя по-другому, это когда они являются специальными делегатами фрейма, такими как Stream.BeginRead
или Socket.BeginSend
. Вместо этого они используют порты завершения ввода-вывода.
Если вы не откручиваете сотни этих задач в среде ASP.NET, я бы рекомендовал просто использовать пул потоков.
ThreadPool.QueueUserWorkItem(s => action());
Или, в .NET 4, вы можете использовать задачу factory:
Task.Factory.StartNew(action);
(Обратите внимание, что приведенное выше будет также использовать пул потоков!)
Созданный компилятором метод BeginInvoke
также вызывается в пуле потоков (ссылка). Поэтому я думаю, что ThreadPool.QueueUserWorkItem будет в порядке, за исключением того, что я думаю, что вы немного более явны (и я полагаю, что будущая CLR могла бы использовать BeginInvoke
'ed методы в другом пуле потоков).
Этот умный парень Скит подходит к этой теме здесь.
Существует другой подход к "стрельбе и забыванию" примерно на полпути вниз.
Вы можете написать свою собственную реализацию threadpool. Вероятно, это звучит как работа, чем на самом деле. Тогда вам не обязательно следовать рекомендациям "только за короткий промежуток времени".
Дайте этому методу расширения выстрел (за С# Это действие .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();