0

For an async method that returns a Task<bool>, I need to take some actions when the method completes. The async method looks like this:

async Task<bool> EntryExists(string entry)
{
  return await Task.Run(() => call_that_returns_bool());
}

I call it and attach a continuation task to it to perform follow-up actions:

EntryExists(my_entry).ContinueWith(t =>
{
  if(t.Result) ...
});

However, I now need to conditionally wait for chained task to complete. Means depending upon a parameter, I either need to return immediately to the caller, or wait till the tasks finish. I change the above code to look like this:

var T = EntryExists(my_entry).ContinueWith(t =>
{
  if(t.Result) ...
});

if(wait) T.Wait(); //wait is my parameter

Running this, the code gets stuck forever at T.Wait() when wait parameter is true, as if T never kicked off. I then tried the following:

var T = EntryExists(my_entry).ContinueWith(t =>
{
  if(t.Result) ...
});

T.Start();
if(wait) T.Wait();

Upon which it tells me that

Start may not be called on a continuation task

I know I'm missing something basic, but after been coding for the last 15 hours, my brain isn't helping much. Can someone point out what I need to do?

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
dotNET
  • 33,414
  • 24
  • 162
  • 251
  • the first snippet is just a waste of memory and cpu.. you can just write `Task.Run(() => call_that_returns_bool()).ContinueWith(cb)`. no need to wrap it with a new coroutine and await. – David Haim Jan 27 '20 at 18:31
  • @DavidHaim: I've simplified it to save everyone else's cpu's. Actual function contains quite a bit of logic. – dotNET Jan 27 '20 at 18:32
  • 2
    ok.. why do you mix await and ContinueWith? use just one of them, preferably await. await the first function, if the await returns `true`, then wait on the task chain. or I missed anything? – David Haim Jan 27 '20 at 18:33
  • @DavidHaim: Because this needs to be called from within a WPF command, which happens to be a property and therefore can't be marked `async`, thus `await` is not an option for me there. – dotNET Jan 27 '20 at 18:36
  • aha. first, remove `T.Start();`, `T` is the task of the callback of `EntryExists`. there is no need to start it, it will start automatically when `EntryExists` is finished and returned a value. – David Haim Jan 27 '20 at 18:37
  • You can make a private async method that does the await conditionally, and then call that synchronously from your WPF property – Andrew Williamson Jan 27 '20 at 18:38
  • @DavidHaim: That's what I hoped and tried first. See second paragraph and code in the post. – dotNET Jan 27 '20 at 18:38
  • WPF + `.Wait` = [deadlock](https://stackoverflow.com/questions/13140523/await-vs-task-wait-deadlock) - please [edit] post to clarify why you disagree with the behavior you obeserve. – Alexei Levenkov Jan 27 '20 at 18:38
  • I don't understand the question, to tell you the truth. if EntryExists returns true, then you need to wait on its callback in the UI thread? sounds like a huge XY problem to me. – David Haim Jan 27 '20 at 18:40
  • @DavidHaim: No, you didn't get it. EntryExists does and will run async. It is just that sometimes I need to wait for it to actually return before doing subsequent actions, while at other times, I simply want it to do its work, while simply returning control to the system to keep UI responsive. – dotNET Jan 27 '20 at 18:44
  • @AlexeiLevenkov: Thanks for the link. That's quite useful stuff. I'll take a nap and generate some more neurons to go through it in the morning. – dotNET Jan 27 '20 at 18:44

1 Answers1

1

You shouldn't block on async code. In short, you're very likely to be blocking the thread that the async method needs to return to (in this case you are, as it's the UI thread). The solution is to use async-await all the way through, rather than trying to run it synchronously. If you absolutely have to present a synchronous method, then at least provide as simpler wrapper as possible, rather than mixing ContinueWith and async-await.

Your outer call can be rewritten as:

async Task<{something}> MakeCallAndContinue()
{
    try
    {
        await EntryExists(my_entry);
        // additional continuation code
    }
    catch (Exception e)
    {
        // handle in some way
    }
}
var task = MakeCallAndContinue();
if (wait) await task;

It's unusual to want to start a task and then not await it, which is what you're doing if wait is false. The error handler I've put in the above code is to ensure that you don't get an exception thrown on to an unawaited task. If you did that, then it would be thrown out somewhere else, and probably kill your process if you haven't declared a global handler.

You won't be able to use the above in your WPF command as-is, because it's async. However, you can have async WPF command handlers, as explained here. If you did want to do it synchronously then you would need to call .Wait(), but as Stephen Cleary explains in my first link, you have to use ConfigureAwait(false) on all awaited tasks all the way down in order to prevent one of them from trying to return to the occupied UI thread, and deadlocking.

gandaliter
  • 9,863
  • 1
  • 16
  • 23
  • Thanks. I'll try it out on my end and get back. Looks like I need to do some reading before deciding how to proceed. – dotNET Jan 27 '20 at 18:48
  • You're welcome. It'll probably be much easier once you've got an async command handler in place. – gandaliter Jan 27 '20 at 18:49