Home > Software engineering >  How can I run several methods (all with onComplete callback) asynchronously one after the onComplete
How can I run several methods (all with onComplete callback) asynchronously one after the onComplete

Time:01-10

I'm using Unity and C# for a small project.

I have a list of person class instances, and each of them has the following method,

public void DoWork(Action onComplete) { }  //onComplete is triggered when everything is done in the method.

I want to run the DoWork methods of all persons asynchronously one after the onComplete of another person DoWork is triggered. That is to say that the first person DoWork asynchronously, and after its onComplete is triggered, the second person DoWork asynchronously, go on untill all persons complete their works.

The method DoWork is currently not an async function, it would be great if I don't need to change this.

I'm wondering how shall I design the code? Is it possible to achieve the purpose without leveraging the .net Task? If not, how can I modify the DoWork method? BTW, Unity doesn't allow me to run the method in a thread (like a Task thread) that is not the main thread.

I tried the following solution. DoWork has an onComplete callback because inside it, there is a tool.Start() function which has an onComplete callback and needs to be executed, DoWork can only be done when tool.Start() onComplete is triggered. So I remoeved onComplete callback for DoWork, and wrote the following code.

public async Task DoWork()
{
    bool allowExit = false;
    tool.Start(() =>        //Start function has an Action onComplete callback, and DoWork can only be done when Start onComplete is triggered
    {
        allowExit = true;
    });
    while (!allowExit) { await Task.Yield(); }
}

public async void AllPersonsDoWork()    //try to run all persons' DoWork methods asynchronously one by one
{
    foreach (var person in personList)
    {
        await person.DoWork();
    }
}

Unity3D doesn't give a threading error, but it freazes.

Any tip is appreicated, thank you very much.

CodePudding user response:

As others have mentioned, you can use Queue and a method to check for Queue size and chain DoWork's to each other. but beware that if an exception occurs in one the DoWork methods all the chain of works will stop.

You can use this sample code:

public Queue<Person> TasksToDo;
    
    public async Task CheckQueue()
    {
        if( TasksToDo.Count > 0){
            var person = TasksToDo.Dequeue();
            await person.DoWork(CheckQueue);
        }
    }
    
    public class Person
    {
        public async Task DoWork(Func<Task> onComplete)
        {
            //..... do work
            if (onComplete != null)
            {
                await onComplete();             
            }
        }
    }

CodePudding user response:

First, I recommend wrapping the DoWork with a TAP wrapper. Callbacks are somewhat similar to events, so you can mostly follow this guidance. An extension method is not too difficult to write:

public static class PersonExtensions
{
  public Task DoWorkAsync(this Person person)
  {
    var tcs = new TaskCompletionSource();
    try
    {
      person.DoWork(() => tcs.TrySetComplete());
    }
    catch (Exception ex)
    {
      tcs.TrySetException(ex);
    }
  }
}

The catch block above only handles synchronous errors. Asynchronous errors aren't handled, since the Action onComplete handler doesn't have a way to represent asynchronous errors. Usually, completion callbacks like this are either Action<Exception?> onComplete or Action onSuccess, Action<Exception> onFailure.

Once you have a DoWorkAsync, calling it for each person is straightforward:

async Task AllPersonsDoWork()
{
  foreach (var person in personList)
    await person.DoWorkAsync();
}
  • Related