How can I run several methods (all with onComplete callback) asynchronously one after the onComplete of another method is triggered?

huangapple go评论78阅读模式
英文:

How can I run several methods (all with onComplete callback) asynchronously one after the onComplete of another method is triggered?

问题

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 until 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 removed 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 freezes.

Any tip is appreciated, thank you very much.

英文:

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.

答案1

得分: 1

其他人已经提到,你可以使用 Queue 和一个方法来检查队列的大小,并将 DoWork 方法链接在一起。但要注意,如果一个 DoWork 方法发生异常,所有工作链都将停止。

你可以使用以下示例代码:

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)
    {
        //..... 进行工作
        if (onComplete != null)
        {
            await onComplete();				
        }
    }
}
英文:

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&lt;Person&gt; TasksToDo;
	
	public async Task CheckQueue()
	{
		if( TasksToDo.Count &gt; 0){
			var person = TasksToDo.Dequeue();
			await person.DoWork(CheckQueue);
		}
	}
	
	public class Person
	{
		public async Task DoWork(Func&lt;Task&gt; onComplete)
		{
			//..... do work
			if (onComplete != null)
			{
				await onComplete();				
			}
		}
	}

答案2

得分: 1

首先,我建议将 DoWork 包装在一个 TAP(任务式异步编程)包装器中。回调函数与事件有些相似,所以您可以大多按照 此指南 进行操作。编写一个扩展方法并不太困难:

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);
    }
    return tcs.Task;
  }
}

上面的 catch 块仅处理同步错误。异步错误没有处理,因为 Action onComplete 处理程序没有表示异步错误的方法。通常,类似这样的完成回调要么是 Action<Exception?> onComplete,要么是 Action onSuccess, Action<Exception> onFailure

一旦您有了 DoWorkAsync,对每个人调用它就很简单了:

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

<details>
<summary>英文:</summary>

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

```C#
public static class PersonExtensions
{
  public Task DoWorkAsync(this Person person)
  {
    var tcs = new TaskCompletionSource();
    try
    {
      person.DoWork(() =&gt; tcs.TrySetComplete());
    }
    catch (Exception ex)
    {
      tcs.TrySetException(ex);
    }
    return tcs.Task;
  }
}

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&lt;Exception?&gt; onComplete or Action onSuccess, Action&lt;Exception&gt; onFailure.

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

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

huangapple
  • 本文由 发表于 2023年1月9日 06:51:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/75051861.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定