A reusable pattern to convert event into task

后端 未结 5 817
陌清茗
陌清茗 2020-11-27 06:58

I\'d like to have a generic reusable piece of code for wrapping EAP pattern as task, something similar to what Task.Factory.FromAsync does for BeginXXX/EndXXX APM pattern.

5条回答
  •  心在旅途
    2020-11-27 07:47

    Converting from EAP to Tasks is not that straightforward, mainly because you have to handle exceptions both when calling the long-running method and when handling the event.

    The ParallelExtensionsExtras library contains the EAPCommon.HandleCompletion(TaskCompletionSource tcs, AsyncCompletedEventArgs e, Func getResult, Action unregisterHandler) extension method to make the conversion easier. The method handles subscribing/unsubscribing from an event. It doesn't try to start the long running operation as well

    Using this method, the library implements asynchronous versions of SmtpClient, WebClient and PingClient.

    The following method shows the general usage pattern:

        private static Task SendTaskCore(Ping ping, object userToken, Action> sendAsync) 
        { 
            // Validate we're being used with a real smtpClient.  The rest of the arg validation 
            // will happen in the call to sendAsync. 
            if (ping == null) throw new ArgumentNullException("ping"); 
    
            // Create a TaskCompletionSource to represent the operation 
            var tcs = new TaskCompletionSource(userToken); 
    
            // Register a handler that will transfer completion results to the TCS Task 
            PingCompletedEventHandler handler = null; 
            handler = (sender, e) => EAPCommon.HandleCompletion(tcs, e, () => e.Reply, () => ping.PingCompleted -= handler); 
            ping.PingCompleted += handler; 
    
            // Try to start the async operation.  If starting it fails (due to parameter validation) 
            // unregister the handler before allowing the exception to propagate. 
            try 
            { 
                sendAsync(tcs); 
            } 
            catch(Exception exc) 
            { 
                ping.PingCompleted -= handler; 
                tcs.TrySetException(exc); 
            } 
    
            // Return the task to represent the asynchronous operation 
            return tcs.Task; 
        } 
    

    The main difference from your code is here:

    // Register a handler that will transfer completion results to the TCS Task 
    PingCompletedEventHandler handler = null; 
    handler = (sender, e) => EAPCommon.HandleCompletion(tcs, e, () => e.Reply, 
              () => ping.PingCompleted -= handler); 
    ping.PingCompleted += handler; 
    

    The extension method creates the handler and hooks the tcs. Your code sets the handler to the source object and starts the long operation. The actual handler type doesn't leak outside the method.

    By separating the two concerns (handling the event vs starting the operation) it's easier to create a generic method.

提交回复
热议问题