-1

Is there a reason for an async Task<> foo() to take time being created? (Just created, awaiting it takes even another chunk of time). When profiling my web service performance I noticed that the mere creation of the task takes a considerable amount of time (in the order of the hundreds of milliseconds).

//DB intensive async tasks, just creating them all takes 600ms-1800ms
Task<Egg> eggTask = FryEggs(2);
Task<Bacon> baconTask = FryBacon(3);
Task<Toast> toastTask = ToastBread(2);
//actually awaiting them takes some hundreds of milliseconds more...
await Task.WhenAll(eggTask, baconTask, toastTask);

The functions in question don't do heavy synchronous work before the first await inside them, so I can't find a compelling reason for them to work this way. Something in this fashion:

async Task<Object> FryEggs(){
    VeryLightweightSynchronousWork();
    await BottleneckingIOWork();
}

I have tried using Task.Factory.StartNew and it does return immediately:

async Task<Object> FryEggs(){
    var res = await Task.Factory.StartNew(async => {
        VeryLightweightSynchronousWork();
        return await BottleneckingIOWork();
    }
    return await res;
}

I don't understand this behaviour.

I actually don't care too much about my specific case, I want to understand why, on a reference-based point of view, should this happen. My current understanding is that a Task starts as soon as it is called, but it should do so asynchronously. So, why would the main thread wait so much for an async task to be created?

Thank you in advance!

chubakueno
  • 565
  • 4
  • 18
  • 1
    awaiting will await for task to complete so it will take time – Sandip Bantawa Apr 24 '19 at 21:49
  • @brykneval yes, but just creating the tasks (before awaiting them) shouln't block the thread so much, as far as I know... – chubakueno Apr 24 '19 at 21:50
  • 7
    without knowledge of what `FryEggs` etc do, there is no possible way we can answer this - `Task` doesn't tell us where that is coming from - it could be http, it could be `Task.Start`, it could be a pre-completed result, etc – Marc Gravell Apr 24 '19 at 21:51
  • Look inside `FryEggs`. Everything from its beginning to the first `await` it performs is going to be executed synchronously. – GSerg Apr 24 '19 at 21:54
  • 1
    @GSerg it can actually be *everything* that is synchronous - some ADO.NET providers are notorious for this. – Marc Gravell Apr 24 '19 at 21:54
  • @GSerg synchronous work before the first await takes 1-2ms at much, i will edit the question to clarify that. – chubakueno Apr 24 '19 at 22:02
  • @MarcGravell If it's not possible to answer the question, why did you post an answer to the question? – Servy Apr 24 '19 at 22:03
  • 2
    "The synchronous work performed by the functions FryEggs,FryBacon and ToastBread before the first await is negligible (1-2 ms)" You have apparently measured that time to be several hundreds of milliseconds, so that's just not true. The question is *what* work are you doing before awaiting anything that's taking several hundred milliseconds, not whether there is any such work. That is unless you're measuring the time *after* declaring these tasks until the method returns it's task to be several hundred milliseconds (meaning `Task.WhenAll` is taking that time) which seems very unlikely. – Servy Apr 24 '19 at 22:08
  • show whats in the fry/toast methods so we can help you more. If I just shell out your stuff with some console writes we are in the 37ms range. – Joe Healy Apr 24 '19 at 22:08
  • @Servy because I think it is possible to answer it *sufficiently*, especially in the context of ADO.NET – Marc Gravell Apr 24 '19 at 22:08
  • 2
    @MarcGravell I don't see anything in the question saying it's ADO.NET, just that there are three methods that return tasks. Also, your answer specifically says that we can't know what the cause is, and instead is just requesting more information, which is what comments are for, not answers. If you thought that you could answer it sufficiently, why is your answer saying we can't know and asking questions of the author instead of stating what the answer is? – Servy Apr 24 '19 at 22:11
  • @Servy see "//DB intensive async tasks"; I think we can agree that ADO.NET is *overwhelmingly* likely to be in play here – Marc Gravell Apr 24 '19 at 22:51
  • @MarcGravell And when the OP updates their question into one that's answerable, due to containing enough information to see what is in fact causing the delay they're observing, we won't need to guess at what *might* be causing the problem, and the question can be able to actually be answered. There are simply *lots* of likely possible causes here, not just one. – Servy Apr 24 '19 at 23:02
  • 5
    @Servy not for the first time, you're trying to police things here, and IMO you're being too pushy and aggressive about it; I have *extensive* expertise in this area, and based on that experience I'm confident that we can get 95% of the way to the answer even on limited data. Even if it isn't right *in this specific case*, the odds are *exceptionally* high (based on having seen similar issues many times) that the question and answer may be useful in future searches. I genuinely don't know why you seem to get so upset by people trying to help people, but: you're laying it on too thick. Really. – Marc Gravell Apr 24 '19 at 23:06
  • Okay, I updated the question to clarify my intent. Don't worry, I don't wan't you to solve my homework (its actually production code, so there is no way I am going to paste it here). I just found an odd picece of behavior that I can't wrap my head around and was asking about why is that happening. – chubakueno Apr 25 '19 at 07:20
  • 1
    @chubakueno: The question is still incomplete. Please post a [minimal, reproducible example](https://stackoverflow.com/help/mcve) that others can paste into Visual Studio and observe what it happening themselves. As it currently stands, if I replace `VeryLightweightSynchronousWork()` with `Thread.Sleep(1);` and replace `BottleneckingIOWork()` with `Task.Delay(2000)`, then the problem does not occur. Whatever is **causing** the problem is still missing from your question. – Stephen Cleary Apr 25 '19 at 13:03
  • @MarcGravell So what do you think is the *approrpriate* response to a moderator fragrantly violating the rules, and responding to anyone telling them that that's not appropriate by just saying that they don't care and no one is allowed to tell them otherwise? The site has decided that it's not appropriate to post an answer to say that you don't know what the problem is and to ask clarifying questions of the author; that's supposed to be posted as a comment. You're not the only one with experience in this area either. I've seen *lots* of instance of this problem with *lots* of different causes. – Servy Apr 25 '19 at 13:09
  • @MarcGravell And what about my comments is "aggressive"? How is it aggressive to ask someone why they answered a question that they feel isn't answerable, or to state that a question doesn't in fact contain information you've claimed that it does? Just saying, "I'm right, I don't care what you think, and even if I'm not right, I don't care anyway" is, in fact, rather aggressive. Posting an answer to say that you don't know the answer *isn't helpful*; **you are not being helpful**, hence why people have a problem with it. – Servy Apr 25 '19 at 13:12
  • @Servy 'And what about my comments is "aggressive"?' seriously? every sentence; I get it that you're opinionated on this topic - that isn't an excuse for your tone, which is **exactly** the unwelcoming hostile tone that we're trying to root out and discourage. It actively harms the site, and users. Now, luckily I'm confident enough in myself (and my understanding of the site's aims and rules) that I'm not easily offended, but: I genuinely think your approach is actively harmful and hostile. Please consider toning it down. – Marc Gravell Apr 25 '19 at 13:20
  • @MarcGravell What do you think is welcoming about the approach of just saying that you're right, you don't care if you're wrong, and no one else is allowed to share an opinion on the topic? Why is that not hostile? Could you provide a reference for your assertion that a question that doesn't have enough information to be answered should have an answer posted saying it's not answerable and requesting more information so that an answer could be given, since you're so knowledgeable about the rules? – Servy Apr 25 '19 at 13:55
  • 1
    @Servy at no point have I said "[I'm] right, [I] don't care if you're wrong, and no one else is allowed to share an opinion on the topic" - I've merely expressed mine (edit: opinion) politely, and listened to yours. And in particular, I've expressed my disagreement with you that it is "not answerable". It might not be a perfect question, but that's not the same thing. – Marc Gravell Apr 25 '19 at 14:30
  • @MarcGravell It's actually [*your* opinion that it's not answerable](https://stackoverflow.com/questions/55839002/net-async-task-taking-too-long-to-create?noredirect=1#comment98341970_55839002). And of course your "answer" says that we can't know what the answer is, and asks questions about the author instead of providing an answer. So *you* are the one asserting it's not answerable (which I agree with), but simply posted an answer anyway (which I don't). – Servy Apr 25 '19 at 14:50
  • You've just responded to statements that your answer isn't an answer by just saying, "you disagree" and "you know the rules". Just responding to explanations of why a given behavior isn't helpful by saying that you disagree, providing no reasoning, and calling people hostile and unhelpful for expressing their opinion, rather than actually providing *any* reasoning for why it's appropriate to post an answer to say a question is unanswerable is not politely expressing one's opinions and listening to people. Rather than talking about how you don't like me, discuss the merits of the issue at hand. – Servy Apr 25 '19 at 14:51
  • 2
    @Servy I'm drawing a line here - you're not listening to me, and you're continuing to be patronizing, hostile, and aggressive. You're free to respond, but I won't: I'm done here. I've said my piece. – Marc Gravell Apr 25 '19 at 15:17
  • @MarcGravell Thanks for proving my point for me that you're entirely unwilling to actually discuss the merits of the issue and hand or listen to anyone else, and are only willing to attack me personally. – Servy Apr 25 '19 at 15:24

2 Answers2

1

just creating them all takes 600ms-1800ms

Most likely, your DB-intensive work is doing some of that work synchronously; perhaps opening the connection, perhaps issuing the command - we can't possibly know. If you show us exactly what FryEggs does, and tell us which ADO.NET provider you're using, we might be able to advise further. Note that some of the ADO.NET providers use async-over-sync for their *Async implementation, which means they're really just synchronous. So the exact ADO.NET provider and version is important to know.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 3
    I know its to long, but shouldn't be that a comment? – Felix K. Apr 24 '19 at 22:07
  • @FelixK. I think it is possible to answer it *sufficiently*, especially in the context of ADO.NET – Marc Gravell Apr 24 '19 at 22:08
  • 1
    @FelixK. I disagree (but I would, or I wouldn't have posted it); it points someone *in the right direction* based on the most likely causes of the problem as stated. Now, we can play imaginary games like "what if the question was perfect and had all the info we needed to give a 100% answer?", but: that doesn't help the real world. And in the real world, sometimes you need to intuit over some chasms between "stated setup", "symptom", "actual cause" and "solution". I'm comfortable that this answer provides the likely bridges. – Marc Gravell Apr 25 '19 at 08:36
0

When an async method returns a Task, it is for the continuation of the method. The beginning of the method is still executed immediately.

async Task FryEggs(int count)
{
    //Anything here will cause FryEggs to block the caller until it is completed

    await DoSomethingAsync();

    //Anything down here is the "continuation" which will execute after the task is returned
}

If you want the incomplete task to be returned immediately, you can add an await at the very beginning, e.g. using Task.Yield:

async Task FryEggs(int count)
{
    await Task.Yield();

    //Now this is part of the continuation too. It won't run until after the Task is returned.

    await DoSomethingAsync();
}

I am not sure this is necessarily the best idea for your scenario, but it is an option.

If FryEggs contains CPU-bound work, and you were hoping to improve things through parallelism, you will probably need to run it on a separate thread:

async Task FryEggs(int count)
{
    await Task.Run( () =>
    {
        //Do something super-expensive and synchronous
    });
    await DoSomethingAsync();
}

This way there is an await at the very beginning so control gets yielded back right away. The expensive stuff is placed on its own thread in the thread pool. The logic can access the same local variables through closures; this is safe as long as you await the Task.Run() call.

async Task FryEggs(int count)
{
    string[] results; //This local variable will be closed over

    await Task.Run( () =>
    {
        //Do something super-expensive and synchronous
        results = GetResults();
    });
    await DoSomethingAsync(results);
}
John Wu
  • 50,556
  • 8
  • 44
  • 80
  • If there is in fact long running synchronous work there, that's still running it all in the current synchronization context, which is likely to cause the same problems. The solution to an asynchronous method having long running synchronous work to do (if in fact it is intended to be long running synchronous CPU bound work) is to explicitly run it in a thread pool thread, meaning `Task.Run`. – Servy Apr 24 '19 at 22:16
  • `DoSomethingAsync` can itself contain something that executes synchronously. So one might think there isn't any blocking code. I think it would be better to emphasise the fact that the code will run synchronously until the first `await` for a non-completed task. – tukaef Apr 24 '19 at 22:25
  • 1
    @Servy `Task.Yield` is also a perfectly acceptable way of explicitly pushing something async - IMO a *better* option than `Task.Run` – Marc Gravell Apr 24 '19 at 22:53
  • @MarcGravell Not of the problem is the current context being blocked, because that would result in the current context still being blocked. – Servy Apr 24 '19 at 22:55
  • I think Servy has a valid point... you should not really be using `Task.Yield()` that much. Better solution is to refactor. I edited my answer to add another option. – John Wu Apr 24 '19 at 22:58
  • 1
    Not a huge fan of either `Yield` or `Task.Run` here, since it is in a web service environment. Most likely `Yield` will still serialize the work on the request context (unless it happens to be Core), and `Task.Run` will interfere with ASP.NET thread pool heuristics. It's tough to give a good answer since the question just doesn't provide sufficient details. – Stephen Cleary Apr 25 '19 at 00:59