Самый простой способ сделать огонь и забыть метод в C # 4.0 - PullRequest
79 голосов
/ 10 апреля 2011

Мне очень нравится этот вопрос:

Самый простой способ сделать огонь и забыть метод в C #?

Я просто хочу знать, что теперь, когда у нас есть параллельные расширения в C # 4.0, есть ли более чистый способ сделать Fire & Forget с Parallel linq?

Ответы [ 4 ]

85 голосов
/ 04 декабря 2013

Не ответ на 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. В большинстве случаев это безвредно - это просто говорит вам, я пытался вернуться, я потерпел неудачу, но вам все равно. Но все еще немного шумно иметь исключения ThreadAbortException либо в ваших журналах в Production, либо в вашем отладчике в локальном dev. .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();
}
82 голосов
/ 11 апреля 2011

С классом Task да, но PLINQ действительно для запросов по коллекциям.

Что-то вроде следующего сделает это с Задачей.

Task.Factory.StartNew(() => FireAway());

Или даже ...

Task.Factory.StartNew(FireAway);

Или ...

new Task(FireAway).Start();

Где FireAway равно

public static void FireAway()
{
    // Blah...
}

Таким образом, в силу краткости имени класса и метода, это превосходит версию пула потоков от шести до девятнадцати символов в зависимости от выбранного вами:)

ThreadPool.QueueUserWorkItem(o => FireAway());
24 голосов
/ 29 июня 2016

У меня есть пара вопросов с основным ответом на этот вопрос.

Во-первых, в реальной ситуации «запусти и забудь» вы, вероятно, не будете 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, не следует полагать, что ненаблюдаемые исключения задач будут безвредными.

5 голосов
/ 10 ноября 2017

Просто чтобы исправить проблему, которая возникнет с ответом Майка Штробеля:

Если вы используете 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, поэтому я могу написать модульные тесты, убедившись, что выполнениезавершено.Вы можете смело игнорировать его при использовании.

...