Come dichiarare un'attività non avviata in attesa di un'altra attività?


9

Ho fatto questo test unitario e non capisco perché "waitit Task.Delay ()" non aspetta!

   [TestMethod]
    public async Task SimpleTest()
    {
        bool isOK = false;
        Task myTask = new Task(async () =>
        {
            Console.WriteLine("Task.BeforeDelay");
            await Task.Delay(1000);
            Console.WriteLine("Task.AfterDelay");
            isOK = true;
            Console.WriteLine("Task.Ended");
        });
        Console.WriteLine("Main.BeforeStart");
        myTask.Start();
        Console.WriteLine("Main.AfterStart");
        await myTask;
        Console.WriteLine("Main.AfterAwait");
        Assert.IsTrue(isOK, "OK");
    }

Ecco l'output del test unitario:

Uscita test unitario

Come è possibile che un "wait" non aspetti e il thread principale continua?


Non è chiaro cosa stai cercando di ottenere. Potete per favore aggiungere l'output previsto?
— OlegI,

1
Il metodo di prova è molto chiaro - isOK dovrebbe essere vero
— Sir Rufo,

Non c'è motivo di creare un'attività non avviata. Le attività non sono thread, usano thread. Cosa stai cercando di fare? Perché non usare Task.Run()dopo il primo Console.WriteLine?
— Panagiotis Kanavos,

1
@Elo hai appena descritto i threadpools. Non è necessario un pool di attività per implementare una coda lavori. Hai bisogno di una coda di lavori, ad esempio oggetti Action <T>
— Panagiotis Kanavos

2
@Elo, ciò che si tenta di fare è già disponibile in .NET, ad esempio tramite le classi TPL Dataflow come ActionBlock o le nuove classi System.Threading.Channels. È possibile creare un ActionBlock per ricevere ed elaborare i messaggi utilizzando una o più attività simultanee. Tutti i blocchi hanno buffer di input con capacità configurabile. Il DOP e la capacità consentono di controllare la concorrenza, limitare le richieste e implementare la contropressione - se vengono messi in coda troppi messaggi, il produttore attende
— Panagiotis Kanavos

Risposte:


8

new Task(async () =>

Un'attività non richiede un Func<Task>, ma un Action. Chiamerà il tuo metodo asincrono e si aspetta che finisca quando ritorna. Ma non lo fa. Restituisce un'attività. Quel compito non è atteso dal nuovo compito. Per la nuova attività, il lavoro viene eseguito una volta restituito il metodo.

È necessario utilizzare l'attività già esistente invece di racchiuderla in una nuova attività:

[TestMethod]
public async Task SimpleTest()
{
    bool isOK = false;

    Func<Task> asyncMethod = async () =>
    {
        Console.WriteLine("Task.BeforeDelay");
        await Task.Delay(1000);
        Console.WriteLine("Task.AfterDelay");
        isOK = true;
        Console.WriteLine("Task.Ended");
    };

    Console.WriteLine("Main.BeforeStart");
    Task myTask = asyncMethod();

    Console.WriteLine("Main.AfterStart");

    await myTask;
    Console.WriteLine("Main.AfterAwait");
    Assert.IsTrue(isOK, "OK");
}

4
Task.Run(async() => ... )è anche un'opzione
— Sir Rufo,

Hai appena fatto lo stesso di un autore della domanda
— OlegI

BTW myTask.Start();rilancierà unInvalidOperationException
— Sir Rufo il

@OlegI Non lo vedo. Potresti spiegarlo per favore?
— nvoigt,

@nvoigt Presumo che significhi myTask.Start()produrre un'eccezione per la sua alternativa e dovrebbe essere rimosso durante l'utilizzo Task.Run(...). Non ci sono errori nella tua soluzione.
— 404

3

Il problema è che stai usando la Taskclasse non generica , che non ha lo scopo di produrre un risultato. Quindi quando si crea l' Taskistanza passando un delegato asincrono:

Task myTask = new Task(async () =>

... il delegato viene trattato come async void. Un async voidnon è una Task, non può essere atteso, la sua eccezione non può essere gestito, ed è una fonte di migliaia di domande fatte dai programmatori frustrati qui in StackOverflow e altrove. La soluzione è utilizzare la Task<TResult>classe generica , perché si desidera restituire un risultato e il risultato è un altro Task. Quindi devi creare un Task<Task>:

Task<Task> myTask = new Task<Task>(async () =>

Ora quando sei Startesterno Task<Task>sarà completato quasi istantaneamente perché il suo compito è solo quello di creare l'interno Task. Dovrai quindi aspettare anche l'interno Task. Ecco come si può fare:

myTask.Start();
Task myInnerTask = await myTask;
await myInnerTask;

Hai due alternative. Se non hai bisogno di un riferimento esplicito all'interiore Task, puoi semplicemente attendere l'esterno Task<Task>due volte:

await await myTask;

... oppure puoi utilizzare il metodo di estensione incorporato Unwrapche combina le attività esterne e interne in una:

await myTask.Unwrap();

Questo scartamento si verifica automaticamente quando si utilizza il Task.Runmetodo molto più popolare che crea attività a caldo, quindi al Unwrapgiorno d'oggi non viene utilizzato molto spesso.

Nel caso in cui decidi che il tuo delegato asincrono deve restituire un risultato, ad esempio a string, devi dichiarare che la myTaskvariabile è di tipo Task<Task<string>>.

Nota: non approvo l'uso di Taskcostruttori per la creazione di attività a freddo. Dato che una pratica è generalmente disapprovata, per ragioni che non conosco davvero, ma probabilmente perché è usata così raramente che ha il potenziale di sorprendere di sorpresa altri utenti / manutentori / revisori del codice.

Consiglio generale: fare attenzione ogni volta che si fornisce un delegato asincrono come argomento a un metodo. Questo metodo dovrebbe idealmente aspettarsi un Func<Task>argomento (nel senso che comprende i delegati asincroni), o almeno un Func<T>argomento (nel senso che almeno il generato Tasknon verrà ignorato). Nel malaugurato caso in cui questo metodo accetti un Action, il tuo delegato verrà trattato come async void. Questo è raramente quello che vuoi, se mai.


Come una risposta tecnica dettagliata! grazie.
— Elo,

@Elo mio piacere!
— Theodor Zoulias,

1
 [Fact]
        public async Task SimpleTest()
        {
            bool isOK = false;
            Task myTask = new Task(() =>
            {
                Console.WriteLine("Task.BeforeDelay");
                Task.Delay(3000).Wait();
                Console.WriteLine("Task.AfterDelay");
                isOK = true;
                Console.WriteLine("Task.Ended");
            });
            Console.WriteLine("Main.BeforeStart");
            myTask.Start();
            Console.WriteLine("Main.AfterStart");
            await myTask;
            Console.WriteLine("Main.AfterAwait");
            Assert.True(isOK, "OK");
        }

inserisci qui la descrizione dell'immagine


3
Ti rendi conto che senza il await, il ritardo dell'attività non ritarderà effettivamente tale attività, giusto? Hai rimosso la funzionalità.
— nvoigt,

Ho appena provato, @nvoigt ha ragione: tempo trascorso: 0: 00: 00,0106554. E vediamo nel tuo screenshot: "tempo trascorso: 18 ms", dovrebbe essere> = 1000 ms
— Elo

Sì, hai ragione, aggiorno la mia risposta. Tnx. Dopo aver commentato, lo risolvo con modifiche minime. :)
— BASKA

1
Buona idea usare wait () e non attendere dall'interno di un'attività! Grazie !
— Elo,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.