Why does this Task return early? Have I done something wrong?

PSGuy

I'm trying to set up a bunch of workers with some minimal coupling, but I'd like to use C# async and tasks. Not all of the tasks will be purely asynchronous (some will be completely synchronous). The motivation for doing this is that I want to create some simple methods that execute business logic, and chain them together using the System.Threading.Tasks.Task API to preserve some notion of ordering. Basically, I want to create a first task, register some continuations, and then wait for the final task to complete.

Here's the simple prototype I built just to see if what I want to do even works:

void Main()
{
    var worker = new Worker();

    var work = worker.StartWork(1, 2000);
    work.ConfigureAwait(false);

    var final = work.ContinueWith(_ => worker.StartWork(2, 0))
        .ContinueWith(ant => worker.StartWork(3, 1500));

    var awaiter = final.ContinueWith(_ => Tuple.Create(_.Id, _.Status));
    Console.WriteLine("\"final\" completed with result {0}", awaiter.Result);
    Console.WriteLine("Done.");
}

// Define other methods and classes here
class Worker {
    internal async Task StartWork(int phase, int delay) {
        Console.WriteLine("Entering phase {0} (in Task {1}) with {2} milliseconds timeout.", phase, Task.CurrentId, delay);
        if (delay > 0)
        {
            Console.WriteLine("Do wait for {0} milliseconds.", delay);
            await Task.Delay(delay);
        }

        Console.WriteLine("ending phase {0}", phase);
    }
}

The problem seems to be in awaiting waiting on the so-called awaiter task:

Entering phase 1 (in Task ) with 2000 milliseconds timeout.
Do wait for 2000 milliseconds.
ending phase 1
Entering phase 2 (in Task 769) with 0 milliseconds timeout.
ending phase 2
Entering phase 3 (in Task 770) with 1500 milliseconds timeout.
Do wait for 1500 milliseconds.
"final" completed with result (770, RanToCompletion)
Done.
ending phase 3

Is this just not supported? I thought I understood the Task API pretty well, but clearly I don't. I think that I can convert this to not use async or task, and just execute the method completely synchronously, but that seems like a bad method of doing things. The continuations that I want to run are not exactly this (they just accept a CancellationToken). There's no particular dependency on messages between tasks - I just need to preserve some notion of ordering.

Thank you.

Edit: I incorrectly used the word awaiting above: I know that accessing Task.Result is completely synchronous. My apologies.

Edit 2: What I expected to occur was that calling ContinueWith(_ => worker.Start(2, 0)) would return a task to ContinueWith, and the TPL would internally await the task returned by worker.StartWork when my user delegate returned a task. Looking at the overload list for ContinueWith, this was clearly incorrect. Part of what I'm trying to solve is how to wait from the Main method that schedules the work; I don't want to exit before all continuations have completed.

The motivation I had for using ContinueWith was that I have requirements similar to the following:

  1. There are three phases to the main method.
  2. Phase 1 creates three workers: a, b, and c.
  3. Phase 2 starts an additional worker d upon the completion of tasks b and c, and another worker e upon completion of a and b (the dependency is that these tasks must be created in this order)
  4. A process similar to this follows until all the work is complete.

If I understand the feedback in the comments correctly, I have essentially two ways I can do this:

  1. Make the methods synchronous, and register the continuations myself.
  2. Leave the methods marked as async Task, and use the await keyword along with the Task.WhenAll API to schedule the continuations.
Eric Lippert

Kevin's answer is good. But based on the comments you seem to have the belief that "continuewith" somehow gives you more power than await when describing the sequencing of a workflow. It does not. Also, the question of how to structure your workflow of your second edit properly without resorting to explicit continuations has not been addressed.

Let's look at your scenario. Your workflow is: we have tasks A, B, C, D and E. Starting D depends on the completion of B and C; starting E depends on the completion of A and B.

Easily done. Remember: await is the sequencing operation on tasks. Any time we wish to say "Y must come after X", we simply put an await X anywhere before Y is started. Conversely, if we do not wish a task to be forced to complete before something, we don't await it.

Here's a tiny little framework to play with; this probably isn't how I would write your real code, but it clearly illustrates the workflow.

    private async Task DoItAsync(string s, int d)
    {
        Console.WriteLine($"starting {s}");
        await Task.Delay(d * 1000);
        Console.WriteLine($"ending {s}");
    }

    private async Task DoItAsync(Task pre1, Task pre2, string s, int d)
    {
        await pre1;
        await pre2;
        await DoItAsync(s, d);
    }

    private async void Form1_Load(object sender, EventArgs e)
    {
        Task atask = DoItAsync("A", 2);
        Task btask = DoItAsync("B", 10);
        Task ctask = DoItAsync("C", 2);
        Task bcdtask = DoItAsync(btask, ctask, "D", 2);
        Task abetask = DoItAsync(btask, atask, "E", 2);
        await bcdtask;
        await abetask;
    }

Follow along. A, B and C are started. (Remember, THEY ARE ALREADY ASYNCHRONOUS. "await" does not make them asynchronous; await introduces a sequencing point in a workflow.)

Next our two helper tasks are started. The preconditions of D are B and C, so we await B. Let's suppose B is incomplete, so the await returns a task to the caller, representing the workflow of "start d after b and c are done, and wait for d to complete".

Now we start our second helper task. Again, it awaits B. Let's again suppose it is incomplete. We return to the caller.

Now we add the final bit of structure to our workflow. The workflow is not complete until the two helper tasks are complete. The two helper tasks are not complete until D and E are complete, and they will not even start until B and C, in the case of D, or B and A, in the case of E, are completed.

Use this little framework to play around with the timings of when stuff completes and you will see that it is very straightforward to build up dependencies in the workflow by using await. That's what it is for. It's called await because it asynchronously waits for a task to complete.

この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。

侵害の場合は、連絡してください[email protected]

編集
0

コメントを追加

0

関連記事

分類Dev

Why does this Task return early? Have I done something wrong?

分類Dev

Why does my method return the wrong value?

分類Dev

Why does get_pages return wrong data and not what I want?

分類Dev

I have a crontab entry that calls a bash script. Why does $USER return blank?

分類Dev

Why does this pclose() implementation return early with ECHILD unless invocation is delayed after popen()?

分類Dev

Why does Double return the wrong data from my DB?

分類Dev

Why does finger not have a non-zero return value?

分類Dev

Why can't I have return types covariant with void*?

分類Dev

I have no idea what is wrong

分類Dev

Why does npm install say I have unmet dependencies?

分類Dev

Why does my glDrawArrays is not drawing the points I have stored?

分類Dev

Why does '/' have an '..' entry?

分類Dev

Why does my program print something before it ends when I press ctrl + D?

分類Dev

Why do I have to specify a return value if the function is already guaranteed to either return or throw?

分類Dev

done()とreturn done()

分類Dev

Why inner task does not execute?

分類Dev

Why does this return False

分類Dev

why is my line break not working in vue? probably something wrong with flex?

分類Dev

Canceled Task does not return control to async block

分類Dev

Is this a bug in the Jackson JsonParser, or am I doing something wrong?

分類Dev

Maven dependencies not applying or am I doing something wrong?

分類Dev

Can I rollback an apt-get upgrade if something goes wrong?

分類Dev

Scripts works perfect when i run it manually, but on crontab something is wrong

分類Dev

Why does Docker have a daemon?

分類Dev

Why does foldr return immediately?

分類Dev

Why does this function return 0

分類Dev

why does find return diff

分類Dev

Something similar to "using" that will create an object and call a method on it when done, but let me do what I want in between

分類Dev

My Paint method is running twice and I have no idea why. How can I fix this, and does anyone know why this is happening?

Related 関連記事

  1. 1

    Why does this Task return early? Have I done something wrong?

  2. 2

    Why does my method return the wrong value?

  3. 3

    Why does get_pages return wrong data and not what I want?

  4. 4

    I have a crontab entry that calls a bash script. Why does $USER return blank?

  5. 5

    Why does this pclose() implementation return early with ECHILD unless invocation is delayed after popen()?

  6. 6

    Why does Double return the wrong data from my DB?

  7. 7

    Why does finger not have a non-zero return value?

  8. 8

    Why can't I have return types covariant with void*?

  9. 9

    I have no idea what is wrong

  10. 10

    Why does npm install say I have unmet dependencies?

  11. 11

    Why does my glDrawArrays is not drawing the points I have stored?

  12. 12

    Why does '/' have an '..' entry?

  13. 13

    Why does my program print something before it ends when I press ctrl + D?

  14. 14

    Why do I have to specify a return value if the function is already guaranteed to either return or throw?

  15. 15

    done()とreturn done()

  16. 16

    Why inner task does not execute?

  17. 17

    Why does this return False

  18. 18

    why is my line break not working in vue? probably something wrong with flex?

  19. 19

    Canceled Task does not return control to async block

  20. 20

    Is this a bug in the Jackson JsonParser, or am I doing something wrong?

  21. 21

    Maven dependencies not applying or am I doing something wrong?

  22. 22

    Can I rollback an apt-get upgrade if something goes wrong?

  23. 23

    Scripts works perfect when i run it manually, but on crontab something is wrong

  24. 24

    Why does Docker have a daemon?

  25. 25

    Why does foldr return immediately?

  26. 26

    Why does this function return 0

  27. 27

    why does find return diff

  28. 28

    Something similar to "using" that will create an object and call a method on it when done, but let me do what I want in between

  29. 29

    My Paint method is running twice and I have no idea why. How can I fix this, and does anyone know why this is happening?

ホットタグ

アーカイブ