Переключить новую задачу (() => {}) для развлечения c<Task> - PullRequest
0 голосов
/ 28 апреля 2020

В ответе на один из моих других вопросов мне сказали, что использование new Task(() => { }) не является чем-то обычным. Мне посоветовали использовать Func<Task> вместо этого. Я пытался заставить это работать, но я не могу понять это. (Вместо того, чтобы перетаскивать его в комментариях, я задаю здесь отдельный вопрос.)

Мой конкретный сценарий c заключается в том, что мне нужно, чтобы задача не запускалась правильно при объявлении * 1008. * и чтобы иметь возможность дождаться его позже.

Вот пример LinqPad, использующий new Task(() => { }). ПРИМЕЧАНИЕ: это работает отлично! (за исключением того, что оно использует new Task.)

static async void Main(string[] args)
{
    // Line that I need to swap to a Func<Task> somehow.
    // note that this is "cold" not started task  
    Task startupDone = new Task(() => { });

    var runTask = DoStuff(() =>
    {
        //+++ This is where we want to task to "start"
        startupDone.Start();
    });

    //+++ Here we wait for the task to possibly start and finish. Or timeout.
    // Note that this times out at 1000ms even if "blocking = 10000" below.
    var didStartup = startupDone.Wait(1000);

    Console.WriteLine(!didStartup ? "Startup Timed Out" : "Startup Finished");

    await runTask;

    Console.Read();
}

public static async Task DoStuff(Action action)
{
    // Swap to 1000 to simulate starting up blocking
    var blocking = 1; //1000;
    await Task.Delay(500 + blocking);
    action();
    // Do the rest of the stuff...
    await Task.Delay(1000);
}

Я попытался поменять вторую строку на:

Func<Task> startupDone = new Func<Task>(async () => { });

Но тогда строки под комментариями с +++ в них не работают правильно.

Я поменял startupDone.Start() на startupDone.Invoke().

Но startupDone.Wait нужна задача. Который возвращается только в лямбду. Я не уверен, как получить доступ к задаче за пределами лямбды, чтобы я мог Wait для нее.

Как можно использовать Func<Task> и запустить его в одной части моего кода и сделать Wait для этого в другой части моего кода? (Как я могу с new Task(() => { })).

Ответы [ 3 ]

2 голосов
/ 29 апреля 2020

Код, который вы опубликовали, не может быть реорганизован для использования Func<Task> вместо холодной задачи, потому что метод, которому нужно await задача (метод Main), не является тем же методом, который контролирует создание / запуск задачи (лямбда-параметр метода DoStuff). Это может сделать использование конструктора Task в этом случае легитимным, в зависимости от того, является ли обоснованным проектное решение о делегировании запуска задачи лямбда-выражению. В этом конкретном примере startupDone используется в качестве примитива синхронизации, чтобы сигнализировать о том, что условие выполнено и программа может продолжаться. Это может быть достигнуто одинаково хорошо при использовании специализированного примитива синхронизации, например, SemaphoreSlim:

static async Task Main(string[] args)
{
    var startupSemaphore = new SemaphoreSlim(0);
    Task runTask = RunAsync(startupSemaphore);
    bool startupFinished = await startupSemaphore.WaitAsync(1000);
    Console.WriteLine(startupFinished ? "Startup Finished" : "Startup Timed Out");
    await runTask;
}

public static async Task RunAsync(SemaphoreSlim startupSemaphore)
{
    await Task.Delay(500);
    startupSemaphore.Release(); // Signal that the startup is done
    await Task.Delay(1000);
}

По моему мнению, использование SemaphoreSlim более целесообразно в этом случае и делает смысл кода более понятным. Он также позволяет await асинхронно передавать сигнал с тайм-аутом WaitAsync(Int32), который не является чем-то, что вы получаете от Task из коробки ( это выполнимо , хотя ).

Использование холодных задач может быть заманчивым в некоторых случаях, но когда вы повторно просматриваете свой код через месяц или два, вы обнаружите, что запутались из-за того, как редко и неожиданно приходится иметь дело с задачами, которые может или еще не был начат.

0 голосов
/ 29 апреля 2020

Я всегда изо всех сил стараюсь никогда не иметь блокирующего поведения при работе с чем-либо асинхронным c или любым типом, который представляет потенциальное асинхронное c поведение, например Task. Вы можете немного изменить свой DoStuff, чтобы облегчить ожидание на Action.


static async void Main(string[] args)
{
    Func<CancellationToken,Task> startupTask = async(token)=>
    {
        Console.WriteLine("Waiting");
        await Task.Delay(3000, token);
        Console.WriteLine("Completed");
    };
    using var source = new CancellationTokenSource(2000);
    var runTask = DoStuff(() => startupTask(source.Token), source.Token);
    var didStartup = await runTask;
    Console.WriteLine(!didStartup ? "Startup Timed Out" : "Startup Finished");
    Console.Read();
}

public static async Task<bool> DoStuff(Func<Task> action, CancellationToken token)
{
    var blocking = 10000;
    try
    {
        await Task.Delay(500 + blocking, token);
        await action();
    }
    catch(TaskCanceledException ex)
    {
        return false;
    }
    await Task.Delay(1000);
    return true;
}

.
0 голосов
/ 28 апреля 2020

Во-первых, тип вашего объекта «сделать это позже» станет Func<Task>. Затем, когда задача запускается (вызывая функцию), вы получаете обратно Task, который представляет операцию:

static async void Main(string[] args)
{
  Func<Task> startupDoneDelegate = async () => { };
  Task startupDoneTask = null;

  var runTask = await DoStuff(() =>
  {
    startupDoneTask = startupDoneDelegate();
  });

  var didStartup = startupDoneTask.Wait(1000);

  Console.WriteLine(!didStartup ? "Startup Timed Out" : "Startup Finished");
}
...