Calling an async method from a non-async method
Asked Answered
M

4

6

Every variation on the following code that I try doesn't work - whether DoSomething() : void and is called as written, or DoSomething() : Task and is called with TaskEx.RunEx(), some attempt involving .GetAwaiter().GetResult(). Errors seen include: "Start may not be called on a task with null action", "RunSynchronously may not be called on a task unbound to a delegate", and "The task has not yet completed".

class Program
{
    static void Main(string[] args) // Starting from a non-async method
    {
        DoSomething();

        Console.WriteLine("Press any key to quit.");
        Console.ReadKey();
    }

    static async void DoSomething()
    {
        Console.WriteLine("Starting DoSomething ...");

        var x = await PrepareAwaitable(1);

        Console.WriteLine("::" + x);

        var y = await PrepareAwaitable(2);

        Console.WriteLine("::" + y);
    }

    static Task<string> PrepareAwaitable(int id)
    {
        return new Task<string>(() =>
        {
            return "Howdy " + id.ToString();
        });
    }
}

Output:

Starting DoSomething ...

Press any key to quit.

PrepareAwaitable's Task's Action will be more complicated later. When this action completes, however long that takes, I would expect the Task (or other Framework mechanisms) to resume by assigning "Howdy ..." to x, and then later to y. What I REALLY want to do is intercept the awaited objects, process them, and at some later time that I control, resume to the continuation with a result (x and y). But I haven't been getting very far on that big step, so I'm trying to start smaller.

Mesencephalon answered 8/12, 2011 at 22:46 Comment(4)
What are you trying to do? It's hard to know what "doesn't work" means when we don't know what "working" would look like.Broadcloth
I want it to run to completion, please. Perhaps it seemed obvious to me what it ought to be doing due to how wrong my understanding is.Mesencephalon
@uosef: Run what though? There's nothing really asynchronous here?Broadcloth
@EricLippert's answer here is really on the mark and is related to this question as well.Mesencephalon
M
6

The tasks you returned haven't started yet (i.e., they're "cold" tasks); try replacing the PrepareAwaitable code with the following:

static Task<string> PrepareAwaitable(int x)
{
    return Task.Factory.StartNew<string>(() =>
    {
        return "Howdy " + x.ToString();
    });
}
Marek answered 8/12, 2011 at 23:3 Comment(1)
Hmm. That works. How can a cold task be started later? When I try .Start() on the original code (If DoSomething() : Task) I get the error "Start may not be called on a task with null action".Mesencephalon
M
13

First, read the Task-Based Asynchronous Pattern document. It's under My Documents\Microsoft Visual Studio Async CTP\Documentation. This document describes how to design APIs naturally consumable by await.

Secondly, realize that there are several aspects of the Task class and related APIs that no longer really apply in the new asynchronous world. Task was originally written as the core of TPL. It turned out to be a good fit for asynchronous programming, so Microsoft used it instead of creating a new "Promise" class. But a number of methods are holdovers, used by TPL but not needed for async programming.

To answer the titular question, mixing synchronous and asynchronous code is not quite straightforward with the current async CTP. I've written a library that includes a Task.WaitAndUnwrapException extension method, which makes it easy to call async code from sync code. You may also be interested in my AsyncContext class, which makes the "press any key" prompt unnecessary.

Melanism answered 9/12, 2011 at 1:12 Comment(1)
This is a really great answer. You illustrate a deeper understanding of my general struggles with async as I'm learning it, and I'd split the points if I could.Mesencephalon
M
6

The tasks you returned haven't started yet (i.e., they're "cold" tasks); try replacing the PrepareAwaitable code with the following:

static Task<string> PrepareAwaitable(int x)
{
    return Task.Factory.StartNew<string>(() =>
    {
        return "Howdy " + x.ToString();
    });
}
Marek answered 8/12, 2011 at 23:3 Comment(1)
Hmm. That works. How can a cold task be started later? When I try .Start() on the original code (If DoSomething() : Task) I get the error "Start may not be called on a task with null action".Mesencephalon
B
5

It's really not clear what you're trying to achieve, because there's nothing asynchronous going on. For example, this will compile and run, but I don't know whether it's what you want:

using System;
using System.Threading.Tasks;

class Program
{
    static void Main(string[] args)
    {
        DoSomething();

        Console.WriteLine("Press any key to quit.");
        Console.ReadKey();
    }

    static async void DoSomething()
    {
        Console.WriteLine("Starting DoSomething ...");

        var x = await PrepareAwaitable(1);

        Console.WriteLine("::" + x);

        var y = await PrepareAwaitable(2);

        Console.WriteLine("::" + y);
    }

    static async Task<string> PrepareAwaitable(int x)
    {
        return "Howdy " + x;
    }
}

Note that this gives a warning for PrepareAwaitable because there's nothing asynchronous in it; no "await" expressions. The whole program executes synchronously. Another alternative implementation of PrepareAwaitable:

static Task<string> PrepareAwaitable(int x)
{
    return TaskEx.Run(() => "Howdy " + x);
}

Is that more like what you were after?

Broadcloth answered 8/12, 2011 at 23:3 Comment(3)
Thanks! TaskEx.Run() is the route I was hoping for. Is this better than CarlosFigueira's Task.Factory.StartNew<string>()? "Better" meaning inline with the async designers intended usage. Which approach has fewer limiting consequences, or are they synonymous?Mesencephalon
@uosɐſ: I believe TaskEx.Run is mostly just a shorthand. There may be some subtle differences, but if there are then I don't know them offhand.Broadcloth
Thanks for the detailed answer! I'd split the points up if I could.Mesencephalon
S
-1

add GetAwaiter().GetResult() after your async task should work. But for eliminating errore like "...not be called on a task with null action", you simply have your Task<IHttpActionResult> to return Ok(true) rather than Ok(). It fixed the GetResult() issue for me !

Spirochaetosis answered 24/3, 2021 at 9:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.