Search code examples
c#asynchronoustask-parallel-librarywcf-ria-servicesopensilver

Converting WCF Web service calls from callback-based async method to awaitable task


I am converting over an old Silverlight application that used callback-based WCF Web Services over to OpenSilver that is using awaitable task based WCF Web Services. I am trying to figure out how to handle the error conditions. Here is the callback-based code:

private void GetNextImage()
{
    var cmc = ServiceFactories.CreateCartManager();
    cmc.getSlideImageCompleted += (s, e) =>
    {
        cmc_getSlideImageCompleted(s, e);
    };

    var lastTime = SystemSettings.GetInstance().SlideShowData.LastImageTime;
    cmc.getSlideImageAsync(string.IsNullOrEmpty(lastTime) ? null : lastTime);
}

void cmc_getSlideImageCompleted(object sender, getSlideImageCompletedEventArgs e)
{
    if (e.Cancelled)
    {
        GetNextImage();
    }
    else if (e.Error != null)
    {
        var errMsg = new ErrorWindow("Error while trying to get next image in slide show:", msg);
        errMsg.Show();
    }
    else if (e.Result == null)
    {
        // There are no images in the slide show right now.
    }
    else
    {
        // we have an image!!!!
        var imageData = e.Result.imageData;
        // <the rest of the code>
    }
}       

I know that GetNextImage() should look like this:

private async Task GetNextImage()
{
    var cmc = ServiceFactories.CreateCartManager();

    var lastTime = SystemSettings.GetInstance().SlideShowData.LastImageTime;
    var result = await cmc.getSlideImageAsync(string.IsNullOrEmpty(lastTime) ? null : lastTime);
    cmc_getSlideImageCompleted(result);
}

void cmc_getSlideImageCompleted(getSlideImageResponse e)
{
    ...
}

The question is, what happened to e.Cancelled, e.Error, and e.Result? How do I account for that lower level errors now?


Solution

  • What happened to e.Cancelled, e.Error, and e.Result?

    e.Cancelled

    If you have an async method (in your case the cmc.getSlideImageAsync) then this can be cancelled through a CancellationToken. Inside this method if you repeatedly check where the cancellation has been requested or not (via the ThrowIfCancellationRequested) then it will throw a OperationCanceledException (or a derived class).

    So, the equivalent of e.Cancelled is this:

    getSlideImageResponse response;
    try
    {
       response = await cmc.getSlideImageAsync(..., cancellationToken);
    }
    catch(OperationCanceledException ex)
    {
       //if(e.Cancelled) logic goes here
    }
    

    e.Error

    If your async method fails for whatever reason then it will populate the underlying Task's Exception property.

    Task<getSlideImageResponse> getTask = cmc.getSlideImageAsync(...);
    getTask.Wait(); //BAD PRACTICE, JUST FOR DEMONSTRATION PURPOSES
    if(getTask.Exception != null)
    {
       //if(e.Error != null) logic goes here
    }
    

    The above code is suboptimal since the .Wait is a blocking call and can cause deadlock. The recommended approach is to use await. This operator can retrieve the .Exception property from the Task and can throw it again:

    getSlideImageResponse response;
    try
    {
       response = await cmc.getSlideImageAsync(...);
    }
    catch(Exception ex)
    {
       //if(e.Error != null) logic goes here
    }
    

    e.Result

    This property was populated only if the method was not cancelled or did not fail. The same is true here:

    getSlideImageResponse response;
    try
    {
       response = await cmc.getSlideImageAsync(..., cancellationToken);
    }
    catch(OperationCanceledException ocex)
    {
       //if(e.Cancelled) logic goes here
    }
    catch(Exception ex)
    {
       //if(e.Error != null) logic goes here
    }
    
    //if(e.Result != null) logic goes here