Search code examples
.net.net-4.0async-ctpasync-awaitc#-5.0

Encapsulating a sync method with async CTP doesn't work


Last year, I wrote a web API library with classic synchronous and asynchronous methods. I'm now trying to add TaskAsync methods using the new C# Async CTP 3.

I wrote this simple code to encapsulate the sync method:

partial class Client : IClient {
    public string Authenticate(string username, string password)
    {
        // long synchronous code here
    }
    public Task<string> AuthenticateTaskAsync(string username, string password) {
        var p = new { username = username, password = password };
        var task = new Task<string>(p1 => this.Authenticate(p.username, p.password), p);
        return task;
    }
}

Then, from my WPF 4 application, I have a async method using it:

public class LoginViewModel {
    private IClient client;

    // called by an ICommand
    private async Task DoLogin(string username, string password) {
        UpdateStatus("Authenticating...", true);
        try {
            var result = await client.AuthenticateTaskAsync(username, password);
            // do stuff with result
            UpdateStatus("Authenticated. Fetching User informations...", true);
        } catch (Exception ex) {
            UpdateStatus("Authentication error.", ex, false);
        }
    }
}

The issue is: my synchronous method never gets called. The debugger goes to result = await client.AuthenticateTaskAsync(username, password);, the debugger continues its work and never comes back. The breakpoint within the sync Authenticate nevers breaks. UpdateStatus never gets called. Quite strange (I though it was a debugger implementation issue).

Then I looked at how WebClient.DownloadStringTaskAsync is implemented. I changed my API client method to this:

partial class Client : IClient {
    public Task<string> AuthenticateTaskAsync(string username, string password) {
        var tcs = new TaskCompletionSource<string>();

        try {
            tcs.TrySetResult(this.Authenticate(username, password));
        } catch (Exception ex) {
            tcs.TrySetException(ex);
        }

        return tcs.Task;
    }
}

And now it works. Can someone explain why the first code doesn't work?


Solution

  • You're creating the task but never starting it. It's created "cold" - it would need something to start it before the function provided to the constructor is actually called.

    Either call Task.Start(), or use TaskEx.Run() or Task.Factory.StartNew() instead of calling the Task constructor:

    public Task<string> AuthenticateTaskAsync(string username, string password) {
        return TaskEx.Run(() => this.Authenticate(username, password));
    }
    

    Note that there's no need for the anonymous type here - just let the compiler capture the parameters.