Мені дуже подобається це питання:
Найпростіший спосіб зробити вогонь і забути метод в C #?
Я просто хочу знати, що тепер, коли у нас є розширення Parallel у C # 4.0, чи є кращий чистіший спосіб зробити Fire & Forget за допомогою Parallel linq?
Мені дуже подобається це питання:
Найпростіший спосіб зробити вогонь і забути метод в C #?
Я просто хочу знати, що тепер, коли у нас є розширення Parallel у C # 4.0, чи є кращий чистіший спосіб зробити Fire & Forget за допомогою Parallel linq?
Відповіді:
Не відповідь для 4.0, але варто зазначити, що в .Net 4.5 ви можете зробити це ще простіше за допомогою:
#pragma warning disable 4014
Task.Run(() =>
{
MyFireAndForgetMethod();
}).ConfigureAwait(false);
#pragma warning restore 4014
Прагма полягає в тому, щоб вимкнути попередження, яке говорить вам про те, що ви виконуєте це завдання як вогонь і забудьте.
Якщо метод всередині фігурних дужок повертає завдання:
#pragma warning disable 4014
Task.Run(async () =>
{
await MyFireAndForgetMethod();
}).ConfigureAwait(false);
#pragma warning restore 4014
Давайте розберемо це:
Task.Run повертає завдання, яке генерує попередження компілятора (попередження CS4014), зазначаючи, що цей код буде виконуватися у фоновому режимі - це саме те, що ви хотіли, тому ми вимикаємо попередження 4014.
За замовчуванням Завдання намагаються "повернути маршала на початкову нитку", це означає, що ця задача буде виконуватись у фоновому режимі, а потім спробувати повернутися до теми, яка її запустила. Часто стріляйте та забувайте Завдання закінчуються після того, як буде виконана оригінальна нитка. Це призведе до того, що ThreadAbortException буде кинуто. У більшості випадків це нешкідливо - це просто кажу вам, я намагався знову приєднатися, я не зміг, але все одно вам все одно. Але все ще трохи галасливо мати ThreadAbortExceptions або у ваших журналах у виробництві, або у налагоджувачі в локальному розробнику. .ConfigureAwait(false)
це лише спосіб дотримуватися впорядкованості та чітко сказати: запустіть це на задньому плані, і все.
Оскільки це багатослівно, особливо потворна прагма, я використовую для цього метод бібліотеки:
public static class TaskHelper
{
/// <summary>
/// Runs a TPL Task fire-and-forget style, the right way - in the
/// background, separate from the current thread, with no risk
/// of it trying to rejoin the current thread.
/// </summary>
public static void RunBg(Func<Task> fn)
{
Task.Run(fn).ConfigureAwait(false);
}
/// <summary>
/// Runs a task fire-and-forget style and notifies the TPL that this
/// will not need a Thread to resume on for a long time, or that there
/// are multiple gaps in thread use that may be long.
/// Use for example when talking to a slow webservice.
/// </summary>
public static void RunBgLong(Func<Task> fn)
{
Task.Factory.StartNew(fn, TaskCreationOptions.LongRunning)
.ConfigureAwait(false);
}
}
Використання:
TaskHelper.RunBg(async () =>
{
await doSomethingAsync();
}
ConfigureAwait(false)
на Task.Run
коли ви не await
завдання. Призначення функції в її назві: "configure await ". Якщо ви не виконуєте await
завдання, ви не реєструєте продовження, і немає коду для завдання, щоб "повернутися до вихідного потоку", як ви говорите. Більший ризик полягає у тому, що неспостережений виняток буде перекинуто на нитку фіналізатора, чого ця відповідь навіть не стосується.
#Disable Warning BC42358
З Task
класом так, але PLINQ справді для запитів над колекціями.
Щось наступне зробить це з Task.
Task.Factory.StartNew(() => FireAway());
Або навіть...
Task.Factory.StartNew(FireAway);
Або ...
new Task(FireAway).Start();
Де FireAway
є
public static void FireAway()
{
// Blah...
}
Отже, в силу стисності імені класу та методу це перевершує версію пулу потоків на шість та дев’ятнадцять символів залежно від вибраного вами :)
ThreadPool.QueueUserWorkItem(o => FireAway());
fire and forget in ASP.NET WebForms and windows.close()
:?
У мене є кілька питань із провідною відповіддю на це питання.
По-перше, у справжній ситуації з пожежею та забуттям ви, мабуть, не await
виконаєте завдання, тому додавати марно ConfigureAwait(false)
. Якщо ви не отримали await
значення, повернене ConfigureAwait
, то воно, можливо, не може мати ніякого ефекту.
По-друге, вам потрібно знати про те, що відбувається, коли завдання завершується з винятком. Розглянемо просте рішення, яке запропонував @ ade-miller:
Task.Factory.StartNew(SomeMethod); // .NET 4.0
Task.Run(SomeMethod); // .NET 4.5
Це вводить небезпеку: якщо невпорядкований виняток уникне з SomeMethod()
цього винятку, він ніколи не буде спостерігатися, і 1 може бути повторно введений у нитку фіналізатора, що призведе до збою вашої програми. Тому я рекомендую використовувати допоміжний метод, щоб забезпечити дотримання будь-яких винятків, що виникають у результаті.
Ви можете написати щось подібне:
public static class Blindly
{
private static readonly Action<Task> DefaultErrorContinuation =
t =>
{
try { t.Wait(); }
catch {}
};
public static void Run(Action action, Action<Exception> handler = null)
{
if (action == null)
throw new ArgumentNullException(nameof(action));
var task = Task.Run(action); // Adapt as necessary for .NET 4.0.
if (handler == null)
{
task.ContinueWith(
DefaultErrorContinuation,
TaskContinuationOptions.ExecuteSynchronously |
TaskContinuationOptions.OnlyOnFaulted);
}
else
{
task.ContinueWith(
t => handler(t.Exception.GetBaseException()),
TaskContinuationOptions.ExecuteSynchronously |
TaskContinuationOptions.OnlyOnFaulted);
}
}
}
Ця реалізація повинна мати мінімальні накладні витрати: продовження викликається лише в тому випадку, якщо завдання не виконується успішно, і його слід викликати синхронно (на відміну від планування окремо від вихідного завдання). У випадку "ледачого" ви навіть не отримаєте асигнування для делегата продовження.
Початок асинхронної операції потім стає тривіальним:
Blindly.Run(SomeMethod); // Ignore error
Blindly.Run(SomeMethod, e => Log.Warn("Whoops", e)); // Log error
1. Це була поведінка за замовчуванням у .NET 4.0. У .NET 4.5 поведінку за замовчуванням було змінено таким чином, що неспостережувані винятки не будуть повторно видалені в потоці фіналізатора (хоча ви все одно можете спостерігати їх через подію UnobservedTaskException на TaskScheduler). Однак конфігурацію за замовчуванням можна замінити, і навіть якщо ваша програма вимагає .NET 4.5, ви не повинні вважати, що невиконані винятки завдань будуть нешкідливими.
ContinueWith
було викликано через скасування, властивістю виключення попередника буде Null і буде викинуто виняток null reference. Встановлення параметра продовження завдання OnlyOnFaulted
усуне необхідність перевіряти нуль або перевіряти, чи є виняток нулем, перед його використанням.
Task
стає деталізацією реалізації.
Task
" - це не те саме, що "Я хочу простий спосіб розпочати фонову операцію, ніколи не спостерігати за її результатом, і мені все одно, який механізм використовується зробити це." На цій підставі я стою за своєю відповіддю на поставлене запитання .
fire and forget
в ASP.NET WebForms і windows.close()
?
Просто щоб виправити якусь проблему, яка трапиться з відповіддю Майка Стробеля:
Якщо ви використовуєте var task = Task.Run(action)
та після того, як призначили продовження для цього завдання, то ви ризикуєте Task
викинути деякий виняток, перш ніж призначити продовження обробника винятків для Task
. Отже, у наведеному нижче класі не повинно бути ризику:
using System;
using System.Threading.Tasks;
namespace MyNameSpace
{
public sealed class AsyncManager : IAsyncManager
{
private Action<Task> DefaultExeptionHandler = t =>
{
try { t.Wait(); }
catch { /* Swallow the exception */ }
};
public Task Run(Action action, Action<Exception> exceptionHandler = null)
{
if (action == null) { throw new ArgumentNullException(nameof(action)); }
var task = new Task(action);
Action<Task> handler = exceptionHandler != null ?
new Action<Task>(t => exceptionHandler(t.Exception.GetBaseException())) :
DefaultExeptionHandler;
var continuation = task.ContinueWith(handler,
TaskContinuationOptions.ExecuteSynchronously
| TaskContinuationOptions.OnlyOnFaulted);
task.Start();
return continuation;
}
}
}
Тут, task
запуск не запускається безпосередньо, натомість він створюється, призначається продовження, і лише тоді виконується завдання, щоб усунути ризик виконання завдання (або викинути якийсь виняток) перед призначенням продовження.
Тут Run
метод повертає продовження, Task
тому я можу писати модульні тести, переконуючись, що виконання завершено. Ви можете сміливо ігнорувати це у своєму використанні.