Calling a task-based asynchronous one way callback

2019-05-28 09:11发布

问题:

I have a WCF service which uses a callback contract to notify the clients, similar to this

public interface IClientCallback
{
    [OperationContract(IsOneWay = true)]
    void NotifySomething();
}

and the service code calling it similar to this

void NotifySomething()
{
    try
    {
        this.callback.NotifySomething();
    }
    catch (Exception ex)
    {
        // Log the exception and eat it
    }
}

Note that by design the callback notification is optional, i.e. nice to have, but not required. That's why it's marked as OneWay and the implementation eats exceptions.

Due to some misunderstanding we were thinking that would be enough for having a non blocking fire and forget method. But of course that wasn't true, so under some circumstances it's blocking for a while, which is causing problems because it's called from inside thread synchronized block. So we decided to make it asynchronous by changing the definition as follows

public interface IClientCallback
{
    [OperationContract(IsOneWay = true)]
    Task NotifySomething();
}

I have no problem with client implementation, my question is how to call it from the service. Here is what I'm thinking to do

async void NotifySomething()
{
    try
    {
        await this.callback.NotifySomething();
    }
    catch (AggregateException ex)
    {
        // Unwrap, log the exception(s) and eat it
    }
    catch (Exception ex)
    {
        // Log the exception and eat it
    }
}

Now, since everyone is saying async void is not a good practice, is it ok to use it here? What other options do I have? What is the recommended way to perform this in the WCF service context?

回答1:

The way you have written it is pretty safe as it handles the exceptions. You could also write a reusable extension method to do that so that you don't need to repeat it.

Perhaps something like this:

public static class Extensions
{
    public static void FireAndForget(this Task task)
    {
        task.ContinueWith(t => 
        {
            // log exceptions
            t.Exception.Handle((ex) =>
            {
                Console.WriteLine(ex.Message);
                return true;
            });

        }, TaskContinuationOptions.OnlyOnFaulted);
    }
}

public async Task FailingOperation()
{
    await Task.Delay(2000);

    throw new Exception("Error");
}   

void Main()
{
    FailingOperation().FireAndForget();

    Console.ReadLine();
}