Click here to Skip to main content
13,801,559 members
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

14.7K views
13 bookmarked
Posted 30 Jan 2017
Licenced CPOL

Helper Class for Calling Asynchronous Methods using Func<T> Generics

, 1 Feb 2017
Rate this:
Please Sign up or sign in to vote.
A helper class to run functions in an asynchronous pattern using the Func<T> delegate

Introduction

There are a lot of articles describing the benefits of Func<T> delegates. What I'm attempting to provide here is a helper class which allows an existing method to be run asynchronously and the value returned directly or passed via a callback delegate.

Background

I was tasked to write a DLL to consume a web API. I began by writing an interface with the methods I'd need and implementing the interface in a class. The class I was writing needed these requirements, each method should be asynchronous, and after finishing, pass the response to a callback delegate to be consumed by the calling application. Here's a typical example of just one of the methods:

public virtual async Task Update(UpdateRequest ur, After a)
{
    var response = new Response();

    try
    {
        // The UpdateRequest finalizes the clients application and a payment request
        // is authorized.  This method is awaitable for responsiveness in the client
        // application.
        e.Status = await Task.Run(() =>
        {
            return service.UpdateApplication(ur);
        });
    }
    catch (Exception ex)
    {
        throw;
    } 
    finally
    {
        a?.Invoke(this, response);
    }
}

After I'd finished writing all the necessary methods, I quickly noticed how each one was very similar:

  1. Each was wrapped in a try catch.
  2. Each implemented an await Task.
  3. Each invoked a callback delegate with the response.
  4. Each used a new Response class.

I thought about this for a while. Wouldn't it be nice if I wrote my methods without a try catch block, without invoking a callback delegate and without awaitable code in every method. Also what about the ability to call a method directly and receive the response. Something like this:

public virtual async Task Update(UpdateRequest ur)
{
    return service.UpdateApplication(ur);
}

The method is simplified already. Now, how to use generics to consume each method passing whatever as the method parameter and receiving whatever as the method return type.

Creating Actor Helper Class

First of all, here's the code to create the generic Actor class.

public class Actor<T, V>
{
    // Optional delegate to use to pass back the 
    public delegate void After(object sender, V e);
    
    // use this delegate to represent a method that can be passed
    // as a parameter without explicitly declaring a custom delegate.
    private Func<T, V> job; 

    public Actor(Func<T, V> f)
    {
        job = f;
    }

    // This method passes T as the parameter to Func<T, V> job and 
    // returns V.  The callback delegate is invoked once the Task has
    // ran to completion.
    public virtual async Task Act(T t, After a)
    {
        V v = await Task.Run(() =>
        {
            return job(t);
        });
            
        a?.Invoke(this, v);
    }

    // This method is the same as above but without invoking a
    // callback delegate. The response is returned directly to
    // to the calling application.
    public virtual async Task<V> Act(T t)
    {
        return await Task.Run(() =>
        {
            return job(t);
        });    
    }
}

I can use this Actor class in many scenarios making any procedural code asynchronous but still retaining control over the response. Here's a simplified approach on how to use the Actor class.

Let's say you have a class named Calculate which has one method named LengthOfString. The add method expects a string as a parameter and simply returns an integer with the length of the input string. I've simulated a long operation by sleeping the thread for 10 seconds.

public class Calculate
{
    public int LengthOfString(string s)
    {
        // sleep the thread to simulate a long operation
        Thread.Sleep(10000);
 
        return s.Length;
    }
}

private async void button1_Click(object sender, EventArgs e)
{
    var calculate = new Calculate();

    // Pass a string as the parameter to calculate.LengthOfString
    // and receive an integer as the response.
    var actor = new Actor<string, int>(calculate.LengthOfString);

    try
    {
        await actor.Act("How long is this string?", StringLength);
    } 
    catch (Exception ex) 
    { 
        MessageBox.Show(ex.Message); 
    }
}

private void StringLength(object sender, int len)
{
    // Show the length of the string in a label control. 
    label1.Text = string.Format("Length of string is {0}.", len);
}

In this example, the label1.Text receives this text "Length of string is 24."

By developing this class, I've increased my knowledge of using Func<T> generic programming, and hopefully it may be of use to you. I've got further reading to do.

Should I expose asynchronous wrappers for synchronous methods?

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

BrettPerry
Engineer
United Kingdom United Kingdom
No Biography provided

You may also be interested in...

Pro

Comments and Discussions

 
QuestionHelp Me Pin
FatCatProgrammer7-Feb-17 5:10
memberFatCatProgrammer7-Feb-17 5:10 
AnswerRe: Help Me Pin
BrettPerry7-Feb-17 9:07
memberBrettPerry7-Feb-17 9:07 
SuggestionImprovements Pin
Richard Deeming30-Jan-17 5:21
mvpRichard Deeming30-Jan-17 5:21 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web02 | 2.8.181215.1 | Last Updated 1 Feb 2017
Article Copyright 2017 by BrettPerry
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid