I was recently working on a Windows Store (Metro) app. I was given a proxy class to communicate with the server. Proxy class was following the callback pattern. What I mean by that is when I invoke a server side method, I pass in an Action<T> that would be invoked when the server call completes.
Personally I now like async / await pattern much more now. It seems more readable and more predictable. It also help me provide better structured error handling. So, I decided to wrap proxy calls inside Task wrappers. I would like to quickly document how to do this in this blog post.
First of all, let’s look at the proxy code method.
public string GetData(Action<ServerResult> callback);
What I would like to have is something like
public async Task<ServerResult> GetData();
To get from one to the other I will use TaskCompletionSource class. It will help me quite a bit. Actually, here is the final version of the wrapper method.
public async Task<ServerResult> GetServerData() { try { var proxy = new Proxy(new Uri("http://mysite.com/getdata")); var taskCompletionSource = new TaskCompletionSource<ServerResult>(); var task = taskCompletionSource.Task; Action<ServerResult> callback = taskCompletionSource.SetResult; await Task.Factory.StartNew(() => { try { proxy.GetData(callback); } catch (Exception exception) { taskCompletionSource.SetException(exception); } }, TaskCreationOptions.AttachedToParent); return await Task.FromResult(new ServerResult(task.Result.Result)); } catch (Exception exception) { return new ServerResult(exception); } }
Let’s walk through the code above. I am creating new instance of my proxy class. Then I am creating task completion source, which is a class that will monitor my results and set my task as completed as well as set the results of that task. I am adding exception handling so that I can cleanly send exceptions to the calling code from both task and general exceptions. Server result class with contain either real data or an exception. Of course, in real app I would have two properties – one for exception, the other for results.
namespace WpfApplicationAsync { public class ServerResult { public ServerResult(object result) { Result = result; } public object Result { get; private set; } } }
To call GetServerData method I can do the following.
private async void Button_Click_1(object sender, RoutedEventArgs e) { MainTextbox.Text = ""; var result = await GetServerData(); Action action = () => { MainTextbox.Text = MainTextbox.Text + result.Result + Environment.NewLine; }; Dispatcher.BeginInvoke(DispatcherPriority.Normal, action); }
I am calling my method GetServerData from my button. Since I am adding all error handling to GetServerData, I do not need to use try/catch. Any exceptions are going to be contained within ServerResult class.
Here is my test code from Proxy class.
public class Proxy { public Proxy(Uri uri) { Uri = uri; } public Uri Uri { get; private set; } public void GetData(Action<ServerResult> callbak) { Thread.Sleep(5000); callbak(new ServerResult("Done")); } }
Again, this blog is just illustrate an approach of converting callbacks to awaitable tasks. I find the code from Button click that awaits the results much cleaner than passing callbacks around.
Pingback: Rethreading a single threaded app. | Musings of an Augmented Reality Philosopher
I have been struggling on how to do this. Thanks!
Pingback: Using async BCL with async function | Ziernicki Answers
When using Task.Factory.StartNew you will create a new thread to wait for the callback. Normally the purpose of async/await pattern is to release a thread while waiting for a I/O response. It probably doesn’t matter in your case, but for others that want to convert from callback to async/await pattern in a code library they should avoid this solution.
I suggest looking here instead:
https://joebuschmann.com/taskcompletionsource-bridging-the-gap-between-old-and-new/
https://blog.rsuter.com/create-task-based-method-from-legacy-callback-method/