SoFunction
Updated on 2025-03-08

Simple implementation of C# asynchronous operation

After .net4.0, parallel computing became extremely simple, but because the company's project development was based on .net3.5, parallel computing and asynchronous programming such as Task could not be used. Therefore, in order to more conveniently develop asynchronous methods in the future, I encapsulated and implemented the asynchronous programming framework, and implemented asynchronous programming through BeginInvoke and EndInvoke.

1. Framework structure

The entire framework consists of four parts

1. Base class abstract operator
I call each asynchronous execution process an Operate, so an Operator is needed to execute
2、FuncAsync
Asynchronous Func
3、ActionAsync
Asynchronous Action
4、Asynchorus
Encapsulation of ActionAsync and FuncAsync

Operator
Operator is an abstract class that implements two interfaces: IOperationAsync and IContinueWithAsync.
IOperationAsync implements asynchronous operations, IContinueWithAsync implements ContinueWith method similar to Task, which continues after the current asynchronous operation is completed.

Detailed explanation of IOperationAsync interface

public interface IOperationAsync
{
  IAsyncResult Invoke();
  void Wait();
  void CompletedCallBack(IAsyncResult ar);
  void CatchException(Exception exception);
}
  • Invoke(): Calling of asynchronous method
  • Wait(): Wait for asynchronous operation to execute
  • CompletedCallBack(): The operation completes the callback
  • CatchException(): Catch exception

IContinueWithAsync interface details

public interface IContinueWithAsync
{
  Operator Previous { get; set; }
  Operator Next { get; set; }
  Operator ContinueWithAsync(Action action);
  Operator ContinueWithAsync<TParameter>(Action<TParameter> action, TParameter parameter);
}

Previous: Previous operation
Next:Next operation
ContinueWithAsync(): Continue operation asynchronously

public abstract class Operator : IOperationAsync, IContinueWithAsync
{
  public IAsyncResult Middle;
  public readonly string Id;
  public Exception Exception { get; private set; }
  public Operator Previous { get; set; }
  public Operator Next { get; set; }
  protected Operator()
  {
    Id = ().ToString();
  }
  public abstract IAsyncResult Invoke();
  protected void SetAsyncResult(IAsyncResult result)
  {
     = result;
  }
  public virtual void Wait()
  {
    if (!) ();
  }
  public virtual void CompletedCallBack(IAsyncResult ar)
  {
  }
  public void CatchException(Exception exception)
  {
     = exception;
  }
  protected Operator ContinueAsync()
  {
    if (Next != null) ();
    return Next;
  }
  public virtual Operator ContinueWithAsync(Action action)
  {
    Next = new ActionAsync(action);
     = this;
    return Next;
  }
  public virtual Operator ContinueWithAsync<TParameter>(Action<TParameter> action, TParameter parameter)
  {
    Next = new ActionAsync<TParameter>(action, parameter);
     = this;
    return Next;
  }
  public virtual Operator ContinueWithAsync<TResult>(Func<TResult> func)
  {
    Next = new FuncAsync<TResult>();
     = this;
    return Next;
  }
  public virtual Operator ContinueWithAsync<TParameter, TResult>(Func<TParameter, TResult> func,
    TParameter parameter)
  {
    Next = new FuncAsync<TParameter, TResult>(func, parameter);
     = this;
    return Next;
  }
}

No return asynchronous operation
ActionAsync

public class ActionAsync : Operator
{
  private readonly Action _action;
  protected ActionAsync()
  {
  }
  public ActionAsync(Action action)
    : this()
  {
    this._action = action;
  }
  public override IAsyncResult Invoke()
  {
    var middle = _action.BeginInvoke(CompletedCallBack, null);
    SetAsyncResult(middle);
    return middle;
  }
  public override void CompletedCallBack(IAsyncResult ar)
  {
    try
    {
      _action.EndInvoke(ar);
    }
    catch (Exception exception)
    {
      (exception);
    }
    ContinueAsync();
  }
}
public class ActionAsync<T> : ActionAsync
{
  public T Result;
  private readonly Action<T> _action1;
  protected readonly T Parameter1;
  public ActionAsync()
  {
  }
  public ActionAsync(T parameter)
  {
    this.Parameter1 = parameter;
  }
  public ActionAsync(Action<T> action, T parameter)
  {
    this._action1 = action;
    this.Parameter1 = parameter;
  }
  public override IAsyncResult Invoke()
  {
    var result = _action1.BeginInvoke(Parameter1, CompletedCallBack, null);
    SetAsyncResult(result);
    return result;
  }
  public override void CompletedCallBack(IAsyncResult ar)
  {
    try
    {
      _action1.EndInvoke(ar);
    }
    catch (Exception exception)
    {
      (exception);
    }
    ContinueAsync();
  }
}

There is a return asynchronous
FuncAsync implements IFuncOperationAsync interface

IFuncOperationAsync

public interface IFuncOperationAsync<T>
{
  void SetResult(T result);
  T GetResult();
}
  • SetResult(T result): Set return value after completion of asynchronous operation
  • GetResult(): Get the return value

1)、FuncAsync

public class FuncAsync<TResult> : Operator, IFuncOperationAsync<TResult>
{
private TResult _result;

public TResult Result
{
  get
  {
    if (! || _result == null)
    {
      _result = GetResult();
    }
    return _result;
  }
}
private readonly Func<TResult> _func1;
public FuncAsync()
{
}
public FuncAsync(Func<TResult> func)
{
  this._func1 = func;
}
public override IAsyncResult Invoke()
{
  var result = _func1.BeginInvoke(CompletedCallBack, null);
  SetAsyncResult(result);
  return result;
}
public override void CompletedCallBack(IAsyncResult ar)
{
  try
  {
    var result = _func1.EndInvoke(ar);
    SetResult(result);
  }
  catch (Exception exception)
  {
    (exception);
    SetResult(default(TResult));
  }
  ContinueAsync();
}
public virtual TResult GetResult()
{
  Wait();
  return this._result;
}
public void SetResult(TResult result)
{
  _result = result;
}
}
public class FuncAsync<T1, TResult> : FuncAsync<TResult>
{
protected readonly T1 Parameter1;
private readonly Func<T1, TResult> _func2;
public FuncAsync(Func<T1, TResult> action, T1 parameter1)
  : this(parameter1)
{
  this._func2 = action;
}
protected FuncAsync(T1 parameter1)
  : base()
{
  this.Parameter1 = parameter1;
}
public override IAsyncResult Invoke()
{
  var result = _func2.BeginInvoke(Parameter1, CompletedCallBack, null);
  SetAsyncResult(result);
  return result;
}
public override void CompletedCallBack(IAsyncResult ar)
{
  try
  {
    var result = _func2.EndInvoke(ar);
    SetResult(result);
  }
  catch (Exception exception)
  {
    CatchException(exception);
    SetResult(default(TResult));
  }
  ContinueAsync();
}
}

Asynchronous asynchronous operation encapsulation
ActionAsync and FuncAsync have laid the foundation for asynchronous operations. The most important thing next is to perform our asynchronous operations through these two classes. For this reason, I encapsulate an asynchronous operation class.
The following parts are mainly encapsulated:

  • WaitAll(IEnumerable<Operator> operations): WaitAll(IEnumerable<Operator> operations): WaitAll
  • WaitAny(IEnumerable<Operator> operations): WaitAny(IEnumerable<Operator> operations): WaitAny(IEnumerable<Operator> operations): WaitAny(IEnumerable<Operator> operations): WaitAny(IEnumerable<Operator> operations): WaitAny(IEnumerable<Operator> operations): WaitAny(IEnumerable<Operator> operations): WaitAny(IEnumerable<Operator> operations): WaitAny(IEnumerable<Operator> operations): WaitAny(IEnumerable<Operator> operations): WaitAny(IEnumerable<Operator> operations): WaitAny(IEnumerable<Operator> operations): WaitAny(IEnumerable<Operator> operations): WaitAny(IEnumerable<Operator> operations): WaitAny(IEnumerable<Operator> operations):
  • ActionAsync
  • FuncAsync
  • ContinueWithAction
  • ContinueWithFunc

The last four contain several overloads, here is just a general method that represents a type of method.
WaitAll

public static void WaitAll(IEnumerable<Operator> operations)
{
foreach (var @operator in operations)
{
  @();
}
}

WaitAny

public static void WaitAny(IEnumerable<Operator> operations)
{
while ((o => !))
  (100);
}

Waiting time can be customized
ActionInvoke

public static Operator Invoke(Action action)
{
Operator operation = new ActionAsync(action);
();
return operation;
}
public static Operator Invoke<T>(Action<T> action, T parameter)
{
Operator operation = new ActionAsync<T>(action, parameter);
();
return operation;
}
public static Operator Invoke<T1, T2>(Action<T1, T2> action, T1 parameter1, T2 parameter2)
{
Operator operation = new ActionAsync<T1, T2>(action, parameter1, parameter2);
();
return operation;
}

FuncInvoke

public static Operator Invoke<TResult>(Func<TResult> func)
{
Operator operation = new FuncAsync<TResult>(func);
();
return operation;
}
public static Operator Invoke<TParameter, TResult>(Func<TParameter, TResult> func, TParameter parameter)
{
TParameter param = parameter;
Operator operation = new FuncAsync<TParameter, TResult>(func, param);
();
return operation;
}
public static Operator Invoke<T1, T2, TResult>(Func<T1, T2, TResult> func, T1 parameter1, T2 parameter2)
{
Operator operation = new FuncAsync<T1, T2, TResult>(func, parameter1, parameter2);
();
return operation;
}

ContinueWithAction

public static Operator ContinueWithAsync(IEnumerable<Operator>operators, Action action)
{
return Invoke(WaitAll, operators)
  .ContinueWithAsync(action);
}
public static Operator ContinueWithAsync<TParameter>(IEnumerable<Operator> operators, Action<TParameter> action, TParameter parameter)
{
return Invoke(WaitAll, operators)
  .ContinueWithAsync(action, parameter);
}

ContinueWithFunc

public static Operator ContinueWithAsync<TResult>(IEnumerable<Operator> operators,Func<TResult> func)
{
return Invoke(WaitAll, operators)
  .ContinueWithAsync(func);
}
public static Operator ContinueWithAsync<TParameter, TResult>(IEnumerable<Operator> operators, 
Func<TParameter, TResult> func, TParameter parameter)
{
return Invoke(WaitAll, operators)
  .ContinueWithAsync(func, parameter);
}

There is a bug here. When ContinueWithAsync is called, Wait cannot be called. Wait originally needed to wait for each asynchronous operation from front to back, but the test did not meet the expected results. However, in theory, there should be no need to operate like this. ContinueWithAsync is just an asynchronous operation that continues to be executed when the previous asynchronous operation is completed. If you want to wait, it is better to put the two operations together and wait in the end, it can still be implemented.
The previous ones are all calls for single-step asynchronous operations. If you need to perform asynchronous operations on a certain set, you can foreach traversal.

public void ForeachAsync(IEnumerbale&lt;string&gt; parameters)
{
  foreach(string p in parameters)
  {
    (Tast,p);
  }
}
public void Test(string parameter)
{
  //TODO: Do something}

Every time I need to write foreach, which is quite troublesome, so it is really necessary to implement parallel computing methods similar to PLinq. However, there is a little difference. PLinq uses multi-core CPU for parallel computing, while the encapsulation only traversal sets for asynchronous operations.
ForeachAction

public static IEnumerable<Operator> Foreach<TParameter>(IEnumerable<TParameter> items, Action<TParameter> action)
{
  return (t => Invoke(action, t)).ToList();
}

ForeachFunc

public static IEnumerable<Operator> Foreach<TParameter, TResult>(IEnumerable<TParameter> items, Func<TParameter, TResult> func)
{
  return (parameter => Invoke(func, parameter)).ToList();
}

How to use
Asynchronous method call without return value

public void DoSomeThing()
{
//TODO:
}

Execute via (DoSomeThing)

public void DoSomeThing(string parameter)
{
//TODO:
}

Execute via (DoSomeThing, parameter)

Asynchronous method call with return value

public string DoSomeThing()
{
//TODO:
}

Execute via (()=>DoSomeThing())

public string DoSomeThing(string parameter)
{
//TODO:
}

Execute through (()=>DoSomeThing(parameter)), or you can pass in parameters through (p=>DoSomeThing(p), parameter)

No return value Foreach

public void Test
{
int[] parameters = {1,2,3,4,5};
(parameters,);
}

There is a return value for Foreach

public void Test
{
int[] parameters = {1,2,3,4,5};
var operators = (parameters,p=> p*2);
(operators);
(<FuncAsync<int,int>>(),
  p=> ());
}

First expand each value of the set by 2 times, and then output
Execute asynchronously

public void Test
{
int[] parameters = {1,2,3,4,5};
var operators = (parameters,p=&gt; p*2);
(operators,,"Execution Completed");
}

Continue execution every time
Maybe sometimes we need to traverse a collection, and after each element is processed, we need to output XX to complete the process.

public void Test
{
int[] parameters = {1,2,3,4,5};
var operators = (parameters,p=&gt; p*2);
(operators,o=&gt;{
  (()={
    //Execute when each element is executed    if( != null)
    {
      //Unhandled exception was generated during previous execution, which can be caught here    }
  });
});
}

Can implement chain asynchronous operation

public void Chain()
{
(,1)
.ContinueWithAsync(,2)
.ContinueWithAsync(,3)
}

This will output 1, 2, 3 according to the steps
Conclusion

The above only lists some of the overloaded methods. Other overloaded methods are nothing more than adding parameters, and the essence is actually the same.

I hope it will be helpful to everyone’s learning. I wish you a happy new year and work hard together in the new year.