C# asyn c задание завершено до его завершения - PullRequest
0 голосов
/ 12 марта 2020

Я разрабатываю сетевое приложение, которое получает данные из веб-сокета, модифицирует их и загружает в службу данных. Загрузка данных занимает некоторое время, и я хотел бы скрыть эту задержку, загружая несколько сообщений одновременно. Когда загрузка завершена, мне нужно отправить подтверждение обратно через веб-сокет.

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

private async Task UploadDataAsync(string data, CancellationToken cancellationToken)
{
    Task uploadTask = new Task(async () =>
    {
        // Simulates uploading data
        await Task.Delay(5000, cancellationToken);
    });

    _ = uploadTask.ContinueWith(async (t1) =>
    {
        // Clean up the task
        await RemoveTask(t1);

        if (t1.IsCompletedSuccessfully)
            await SendAck(this, data, cancellationToken);
        else if (t1.IsFaulted)
            logger.LogError(t1.Exception, $"An error occurred while uploading {data}");
        else if (t1.IsCanceled)
            logger.LogWarning($"An upload task {data} was canceled");

    }, TaskScheduler.Default);

    await AddTask(uploadTask);
    uploadTask.Start();
}

В предыдущем коде подтверждение отправляется до загрузки данных. Удивительно, но это потому, что в моей Задаче используется асинхронная лямбда c По причине, которую я не понимаю, uploadTask завершается, когда ожидается загрузка. Поэтому я изменил это на что-то вроде этого:

private async Task UploadDataAsync(string data, CancellationToken cancellationToken)
{
    Task uploadTask = new Task(() =>
    {
        // Simulates uploading data
        Task.Delay(5000, cancellationToken).Wait();
    });

    _ = uploadTask.ContinueWith((t1) =>
    {
        // Clean up the task
        RemoveTask(t1).Wait();

        if (t1.IsCompletedSuccessfully)
            SendAck(this, data, cancellationToken).Wait();
        else if (t1.IsFaulted)
            logger.LogError(t1.Exception, $"An error occurred while uploading {data}");
        else if (t1.IsCanceled)
            logger.LogWarning($"An upload task {data} was canceled");

    }, TaskScheduler.Default);

    await AddTask(uploadTask);
    uploadTask.Start();
}

Теперь все выполняется в правильном порядке, за исключением случаев, когда что-то не так go или операция отменена (например, сервер выключен). Теперь я имею дело с AggregateExceptions и TaskCanceledExceptions.

Кажется, это должно быть проще, чем я это делаю. Я делаю это неправильно?

Редактировать Добавление псевдокода, который вызывает UploadDataAsync для контекста.

protected override async Task DoConnection(CancellationToken cancellationToken)
{
    while (_websocket.State == WebSocketState.Open && !cancellationToken.IsCancellationRequested)
    {
        // Simulates getting websocket data
        string result = await _websocket.ReceiveAsync();

        // This should only asynchronously wait for the upload task to get
        // created. It should not wait for the upload to complete.
        await OnDataReceived(result, cancellationToken);
    }
}

Ответы [ 2 ]

1 голос
/ 13 марта 2020

Проблема с конструктором Task, имеющим asyn c делегат, состоит в том, что он возвращает Task<Task>, а не Task. Вам разрешено привести Task<Task> к Task, потому что класс Task<TResult> наследуется от Task, но при этом вы теряете ссылку на внутреннюю задачу. И в вашем случае внутренняя задача делает всю работу. Внешняя задача просто запускает внутреннюю задачу, которая является крошечной работой, измеряемой в наносекундах. Как только код достигает первого await, внешняя задача выполнила свою работу и сигнализирует о завершении. Если вы хотите сохранить конструктор Task в своем решении (хотя эксперты не одобряют его использование), вы должны изменить свой код так:

Task<Task> uploadTaskFactory = new Task(async () =>
{
    await Task.Delay(5000, cancellationToken); // Simulates an I/O operation
});

//...
uploadTaskFactory.Start();
Task uploadTask = await uploadTaskFactory; // takes essentially zero time

//...
await uploadTask; // takes 5 seconds
1 голос
/ 12 марта 2020

Прямо сейчас, вы звоните uploadTask.Start() и просто продолжаете, даже не дожидаясь его окончания sh. (Даже AddTask происходит до , когда вы звоните uploadTask.Start()). Вам следует дождаться uploadTask, а не просто запустить его и сразу же двигаться дальше.

...