4

I have custom control and I have interface this control exposes to it's users.

public interface ILookupDataProvider
{
    string IdColumnName { get; }

    IEnumerable<IDataColumn> Metadata { get; set; }

    void GetDataAsync(string parameters, 
        Action<IEnumerable<object>> onSuccess, Action<Exception> onError);
}

So, it's my attempt to expose async operation in GetDataAsync

But I don't know how to implement this method in my class that implements interface. I understand this portion as I have method which will execute and then onCompletion, onSucess or onError delegate will be called.

Can somebody help with syntax on how to write those?

EDIT:

It's 4.0 and I can't use await commands

EDIT 2:

I use DevForce framework to load data, but for the sake of this sample - let's do WCF service for example. How would I wrap WCF service call in my interface implementation?

Also, do you think it's OK to create interface like this to present async operation? Would you do it differently with event for example?

Ting
  • 1,658
  • 16
  • 26
katit
  • 17,375
  • 35
  • 128
  • 256
  • Are you after the usage of async/await or what exactly do you want to know? – flq Nov 07 '11 at 20:56
  • Are you targetting .NET 3.5 or .NET 4 – Alois Kraus Nov 07 '11 at 20:56
  • I can't use async await because we are in production with VS2010 I want to write implementation for this interface and can't figure out syntax. Even simple non-async implementation of it should help me understand how to code against Action – katit Nov 07 '11 at 20:57
  • It'll depend on what `GetDataAsync` does. Is it reading from disk? Making a WCF call? Querying a database? Whatever it's calling probably has an async API, and you would implement `GetDataAsync` in terms of that API. To help you, we'd need to know which API you'll be using. – Joe White Nov 07 '11 at 21:07
  • @Joe I edited main post to give more details on what I do. – katit Nov 07 '11 at 21:12

3 Answers3

6

The basic idea here is that the query will occur an a background thread. Once your operation is complete you would use the onSuccess and onError callbacks in order to report the new values. For example

void GetDataAsync(
  string parameters, 
  Action<IEnumerable<object>> onSuccess, 
  Action<Exception> onError) {

  WaitCallback doWork = delegate { 
    try { 
      IEnumerable<object> enumerable = GetTheData(parameters);
      onSuccess(enumerable);
    } catch (Exception ex) {
      onError(ex);
    }
  };

  ThreadPool.QueueUserWorkItem(doWork, null);
}
JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • 1
    Don't know why `Task` or `Task` isn't used, the whole point of these classes is to push the control of the notification of the completion of the async operation outside of the logic of the method. – casperOne Nov 07 '11 at 21:15
3

You really don't want to use this pattern:

void GetDataAsync(string parameters, 
    Action<IEnumerable<object>> onSuccess, Action<Exception> onError);

Instead, you want to use this:

Task GetDataAsync(string parameters);

In returning a Task, you are returning an instance which represents the asynchronous unit of work. From there, the consumer of the API can choose to call ContinueWith and decide what to do when it succeeds, or if there is an error.

However, there is a design flaw in your example. In a method named GetDataAsync, I'd expect data to be returned. That's not a problem, you can just change the signature to:

Task<MyData> GetDataAsync(string parameters);

This now returns a Task<T> which you can use the Result property of to get the result (it will block if the task isn't done), or you can use the ContinueWith method again to process the data when the async operation is done.

Additionally, you can take a CancellationToken structure instance of a parameter to determine if you should cancel your operation:

Task<MyData> GetDataAsync(string parameters, 
    CancellationToken cancellationToken);

Again, ContinueWith will allow you to indicate the action to take on cancellation.

Note that this is the way how methods using await and async in the Async CTP are currently being modeled; they are returning Task or Task<T>; doing the same in your code will allow you to be ready when these language features are baked in.

casperOne
  • 73,706
  • 19
  • 184
  • 253
  • I feel that you correct, however your example is even more complex to get my head around :) I know I have to but still. I don't think I can use it unless you show me how would I implement it in class that implements interface and for example wraps WCF call into this. I don't care about cancelling and continue operations. I just need to know when it finished either with exception or with data – katit Nov 07 '11 at 21:21
  • Ok, is this available in Silverlight? I think Task is not available – katit Nov 07 '11 at 21:29
1

To use tasks you can use it like this. The only tricky thing to remember is to execute the callback in your UI thread which is achieved with TaskScheduler.FromCurrentSynchronizationContext() so you can update your UI or display a messagebox if something went wrong.

Due to the event driven nature of this stuff it can happen that if you hammer the button which does start the WCF calls that you get the results not back in the order you did send the requests. You can prevent this by storing the started task and cancel the last started task if you want to start a new operation or you can simply ignore the subsequent requests while a task is running.

private void button1_Click(object sender, EventArgs e)
{
    GetDataAsync("www.data.com").ContinueWith(result =>
        {
            if (result.Exception != null)
            {
                MessageBox.Show(this, "Error: {0}" + result.Exception, "Error");
            }
            else
            {
                foreach (var obj in result.Result)
                {
                    textBox1.Text += obj.ToString();
                }
            }
        },
        TaskScheduler.FromCurrentSynchronizationContext()
        );

}

Task<IEnumerable<object>> GetDataAsync(string parameters)
{
    return Task<IEnumerable<object>>.Factory.StartNew(() =>
    {
        Thread.Sleep(500);
      //  throw new ArgumentException("uups");
        // make wcf call here
        return new object[] { "First", "second" };
    });
}
Alois Kraus
  • 13,229
  • 1
  • 38
  • 64