Question

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?

Was it helpful?

Solution

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);
}

OTHER TIPS

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.

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" };
    });
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top