Naprawdę podoba mi się to pytanie:
Najprostszy sposób na wykonanie metody „odpal i zapomnij” w języku C #?
Chcę tylko wiedzieć, że teraz, gdy mamy rozszerzenia równoległe w C # 4.0, czy istnieje lepszy, czystszy sposób na wykonanie polecenia Fire & Forget z Parallel linq?
c#
.net
.net-4.0
task-parallel-library
fire-and-forget
Jonathon Kresner
źródło
źródło
Odpowiedzi:
Nie jest to odpowiedź na 4.0, ale warto zauważyć, że w .Net 4.5 możesz to jeszcze uprościć dzięki:
#pragma warning disable 4014 Task.Run(() => { MyFireAndForgetMethod(); }).ConfigureAwait(false); #pragma warning restore 4014
Pragmą jest wyłączenie ostrzeżenia, które mówi, że uruchamiasz to zadanie jako uruchom i zapomnij.
Jeśli metoda wewnątrz nawiasów klamrowych zwraca Task:
#pragma warning disable 4014 Task.Run(async () => { await MyFireAndForgetMethod(); }).ConfigureAwait(false); #pragma warning restore 4014
Rozbijmy to:
Task.Run zwraca Task, który generuje ostrzeżenie kompilatora (ostrzeżenie CS4014) informujące, że ten kod będzie uruchamiany w tle - dokładnie tego chciałeś, więc wyłączamy ostrzeżenie 4014.
Domyślnie zadania podejmują próbę „Marshal z powrotem do oryginalnego wątku”, co oznacza, że to zadanie będzie działać w tle, a następnie spróbuje powrócić do wątku, który je uruchomił. Często uruchamiaj i zapominaj o zakończeniu zadań po zakończeniu oryginalnego wątku. Spowoduje to zgłoszenie wyjątku ThreadAbortException. W większości przypadków jest to nieszkodliwe - po prostu ci mówi, że próbowałem dołączyć, zawiodłem, ale i tak cię to nie obchodzi. Ale nadal jest trochę głośno, aby mieć ThreadAbortExceptions w dziennikach w produkcji lub w debugerze w lokalnym programie deweloperskim.
.ConfigureAwait(false)
to tylko sposób na zachowanie porządku i jednoznaczne stwierdzenie, że uruchom to w tle i to wszystko.Ponieważ jest to rozwlekłe, zwłaszcza brzydka pragma, używam do tego metody bibliotecznej:
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); } }
Stosowanie:
TaskHelper.RunBg(async () => { await doSomethingAsync(); }
źródło
ConfigureAwait(false)
wTask.Run
jeśli nie zrobićawait
zadanie. Celem funkcji jest w jej nazwie: „configure await ”. Jeśli nie wykonaszawait
zadania, nie zarejestrujesz kontynuacji i nie ma kodu, który pozwoliłby, jak to powiedzieć, „przenieść z powrotem do oryginalnego wątku”. Większe ryzyko wiąże się z ponownym zgłoszeniem niezauważonego wyjątku w wątku finalizatora, którego ta odpowiedź nawet nie dotyczy.#Disable Warning BC42358
Z
Task
klasą tak, ale PLINQ jest tak naprawdę do przeszukiwania kolekcji.Coś takiego jak poniższe zrobi to z Task.
Lub nawet...
Lub...
new Task(FireAway).Start();
Gdzie
FireAway
jestpublic static void FireAway() { // Blah... }
Tak więc dzięki zwięzłości nazwy klasy i metody przewyższa wersję puli wątków o od sześciu do dziewiętnastu znaków w zależności od tego, który wybierzesz :)
źródło
fire and forget in ASP.NET WebForms and windows.close()
:?Mam kilka problemów z wiodącą odpowiedzią na to pytanie.
Po pierwsze, w prawdziwej sytuacji „ odpal i zapomnij” prawdopodobnie nie wykonasz
await
zadania, więc dodawanie jest bezużyteczneConfigureAwait(false)
. Jeśli nie zwróciszawait
wartości zwróconej przezConfigureAwait
, nie może to mieć żadnego efektu.Po drugie, musisz być świadomy tego, co się dzieje, gdy zadanie kończy się z wyjątkiem. Rozważ proste rozwiązanie, które zasugerował @ ade-miller:
Task.Factory.StartNew(SomeMethod); // .NET 4.0 Task.Run(SomeMethod); // .NET 4.5
Wprowadza to zagrożenie: jeśli nieobsługiwany wyjątek ucieka z
SomeMethod()
, ten wyjątek nigdy nie zostanie zaobserwowany i może 1 zostać ponownie zgłoszony w wątku finalizatora, powodując awarię aplikacji. Dlatego zalecałbym użycie metody pomocniczej, aby zapewnić przestrzeganie wszelkich wynikających z tego wyjątków.Możesz napisać coś takiego:
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); } } }
Ta implementacja powinna mieć minimalne obciążenie: kontynuacja jest wywoływana tylko wtedy, gdy zadanie nie zakończy się pomyślnie, i powinna być wywoływana synchronicznie (w przeciwieństwie do planowania oddzielnie od oryginalnego zadania). W przypadku „leniwego” nie poniesiesz nawet alokacji dla delegata kontynuacji.
Rozpoczęcie operacji asynchronicznej staje się wtedy trywialne:
Blindly.Run(SomeMethod); // Ignore error Blindly.Run(SomeMethod, e => Log.Warn("Whoops", e)); // Log error
1. To było domyślne zachowanie w .NET 4.0. W programie .NET 4,5 domyślne zachowanie zostało zmienione w taki sposób, że niezauważone wyjątki nie będą ponownie zgłaszane w wątku finalizatora (chociaż można je nadal obserwować za pośrednictwem zdarzenia UnobservedTaskException w TaskScheduler). Jednak domyślną konfigurację można zastąpić, a nawet jeśli aplikacja wymaga .NET 4.5, nie należy zakładać, że niezauważone wyjątki zadań będą nieszkodliwe.
źródło
ContinueWith
została wywołana z powodu anulowania, właściwość wyjątku poprzednika będzie miała wartość Null i zostanie zgłoszony wyjątek odwołania o wartości null. Ustawienie opcji kontynuacji zadania naOnlyOnFaulted
eliminuje potrzebę sprawdzania wartości null lub sprawdzania, czy wyjątek ma wartość null przed jego użyciem.Task
staje się szczegółem implementacji.Task
” to nie to samo, co „Chcę w prosty sposób uruchomić operację w tle, nigdy nie obserwować jej wyniku i nie obchodzi mnie, jaki mechanizm jest używany aby to zrobić ”. Na tej podstawie popieram swoją odpowiedź na zadane pytanie .fire and forget
w ASP.NET WebForms iwindows.close()
?Aby rozwiązać problem, który wystąpi z odpowiedzią Mike'a Strobela:
Jeśli użyjesz
var task = Task.Run(action)
i po przypisaniu kontynuacji do tego zadania, pojawi się ryzykoTask
wyrzucenia wyjątku przed przypisaniem kontynuacji obsługi wyjątków doTask
. Zatem poniższa klasa powinna być wolna od tego ryzyka: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; } } }
W tym przypadku
task
nie jest uruchamiany bezpośrednio, zamiast tego jest tworzony, przypisywana jest kontynuacja, a dopiero potem uruchamiane jest zadanie, aby wyeliminować ryzyko zakończenia wykonywania (lub wyrzucenia wyjątku) przed przypisaniem kontynuacji.Run
Metoda tutaj zwraca kontynuacjęTask
, więc jestem w stanie pisać testy jednostkowe upewniając się, że wykonanie jest kompletny. Możesz jednak bezpiecznie zignorować to w swoim użyciu.źródło