Rozwijam aplikację sieciową, która odbiera dane z serwisu WebSocket, modyfikuje go i przesłania go do usługi danych. Przesyłanie danych zajmuje trochę czasu i chciałbym ukryć tę opóźnienie, przesyłając kilka wiadomości jednocześnie. Gdy przesyłanie jest zakończone, muszę wysłać potwierdzenie z powrotem przez WebSocket.

Stworzyłem kolejkę pracy, aby utrzymać wszystkie zaległe zadania. Wydaje się, że działa dobrze, więc nie uwzględniłem go. Ale moje zadanie przesyłania wydaje się kończyć, zanim faktycznie się skończy. Oto przycięta próbka.

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();
}

W poprzednim kodzie potwierdzenie jest wysyłane przed przesłaniem danych. Zaskakująco, że wydaje się, że mój zadanie używa asynowego lambda. Z jakiegoś powodu nie rozumiem uploadTask uzupełnia po oczekiwaniu przesyłania. Zmieniłem to do czegoś takiego:

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();
}

Teraz wszystko wykonuje w odpowiedniej kolejności, z wyjątkiem sytuacji, gdy rzeczy pójdą źle lub operacja jest anulowana (np. Serwer jest wyłączony). Teraz mam do czynienia z agregatami i zadańcanceledexceptions.

Wygląda na to, że powinno być łatwiejsze niż to robię. Czy robię to źle?

Edytuj Dodawanie pseudokodu, który wywołuje UploadDataAsync dla kontekstu.

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);
    }
}
1
Brian Heilig 12 marzec 2020, 21:06

2 odpowiedzi

Najlepsza odpowiedź

Problem z konstruktorem Task mający delegat asynchroniczny jest taki, że zwraca Task<Task>, a nie a Task. Możesz rzucić Task<Task> do Task, ponieważ {X5}} Klasa dziedziczy z Task, ale w ten sposób tracisz odniesienie do wewnętrznego zadania. W twoim przypadku wewnętrzne zadanie wykonuje całą pracę. Zadanie zewnętrzne rozpoczyna się po prostu wewnętrzne zadanie, co jest minusłową pracą mierzoną w nanosekundach. Gdy tylko kod uderzy w pierwszą await, zadanie zewnętrzne wykonało swoją pracę i sygnalizuje jego zakończenie. Jeśli chcesz zachować konstruktor Task w rozwiązaniu (chociaż jego użycie jest zmarszczeni przez ekspertów), powinieneś zmodyfikować swój kod do czegoś takiego:

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
Theodor Zoulias 19 sierpień 2020, 06:12

W tej chwili zadzwonisz uploadTask.Start() i po prostu kontynuuj, nie czekając na to, aż skończy. (Nawet AddTask zdarza się przed nazywasz uploadTask.Start()). Powinieneś czekać uploadTask, zamiast go uruchomić i przejść od razu.

1
EJoshuaS - Reinstate Monica 12 marzec 2020, 18:14