简体   繁体   English

等待任务完成失败 - ASP.NET Web API 微服务

[英]Fail to wait for Task completion - ASP.NET Web API Microservices

I have an interesting problem and I am new to async programming, so any criticism is accepted.我有一个有趣的问题,而且我是异步编程的新手,所以接受任何批评。 I have 2 APIs communicating with each other.我有 2 个 API 相互通信。 The main idea for what I am trying to create is when a user tries to complete his shopping cart - the cart API asks the catalogue API for available quantities.我要创建的主要思想是当用户尝试完成他的购物车时 - 购物车 API 向目录 API 询问可用数量。 And since it is a message (RabbitMQ) I want to wait for this message to be received back to my cart API, processed and then return to the control flow of the method in the cart API for payment execution to continue.并且由于它是一条消息(RabbitMQ),我想等待这条消息被接收回我的购物车 API,进行处理然后返回购物车 API 中的方法的控制流程以继续付款执行。 I have 3 classes which are involved: CheckoutShoppingCart - which executes the payment, asks Catalogue API for available quantities.我有 3 个涉及的类: CheckoutShoppingCart - 执行付款,向目录 API 询问可用数量。 SessionState - holds temporary data for a product/ logged user (Singleton, injected in the 2 classes). SessionState - 保存产品/登录用户的临时数据(单例,注入 2 个类)。 EventProcessor - processes incoming RabbitMQ messages. EventProcessor - 处理传入的 RabbitMQ 消息。

According to the official documentation of RabbitMQ they use something called correlationId.根据 RabbitMQ 的官方文档,他们使用了一个名为correlationId的东西。 But I decided to create a workaround, I succeeded to some degree, but the code fails for a very unexpected reason.但是我决定创建一个解决方法,我在某种程度上成功了,但是代码由于一个非常意想不到的原因而失败。

The first method below is the one which sends message through RabbitMQ to the Catalogue API.下面的第一种方法是通过 RabbitMQ 向目录 API 发送消息的方法。

public class CheckoutShoppingCart 
{
   await PublishMessageAvailableQuantities(loggedUserCart.Products);
   await ProductQuantityEvaluation();
}

Then the second one is the one who is failing and this is its body:然后第二个是失败的,这是它的身体:

private async Task ProductQuantityEvaluation()
{
 Task.WhenAny(sessionState.CompleteTask());
}

Now, the task within WhenAny - sessionState.CompleteTask() is a dummy method which I created as a flag in the message processor class I have, which processes information messages received from the catalogue API.现在,WhenAny - sessionState.CompleteTask() 中的任务是我在我拥有的消息处理器 class 中创建的一个虚拟方法,它处理从目录 API 接收到的信息消息。 When the message is successfully received the processor executes this dummy method.成功接收到消息后,处理器将执行此虚拟方法。 And thats why I follow its status - to see if it is executed.这就是为什么我关注它的状态 - 看看它是否被执行。

So, when someone clicks to execute the payment of the shopping cart, I want to reach the Task.WhenAny which would wait for this task to be completed by the message processor class, which means that the message for the available quantities is received.因此,当有人点击执行购物车付款时,我想访问 Task.WhenAny,它会等待消息处理器 class 完成此任务,这意味着收到了可用数量的消息。 When it is received the control flow would continue in the class which executes the payment of the shopping cart.当收到它时,控制流程将在执行购物车付款的 class 中继续。

Everything is working with Task.Wait(10000).一切都在使用 Task.Wait(10000)。 While this task is waiting, other thread works on the processing of the message, when it is done the control flow returns here.在此任务等待期间,其他线程处理消息,完成后控制流返回此处。 But when I use the WhenAny, when I debug - for some reason the W henAny(sessionState.CompleteTask()) invokes the method itself , and when I debug the method sessionState.CompleteTask() it actually gets invoked before the event processor invocation.但是当我使用WhenAny时,当我调试时——由于某种原因, WhenAny(sessionState.CompleteTask()) 调用方法本身,而当我调试方法 sessionState.CompleteTask() 时,它实际上在事件处理器调用之前被调用。 But from code perspective it is only invoked from the message processor.但从代码的角度来看,它只能从消息处理器中调用。

One of the cases of the Event processor:事件处理器的一种情况:

public class EventProcessor 
{
case AppConstants.eventTypeSendActualProductQuantities:
                    var disapprovedProductInfo = JsonSerializer.Deserialize<PublishedProductModel>(message);
                    sessionState.publishedProductModel = disapprovedProductInfo;
                    sessionState.productQuantityStatus = 2;
                    **await sessionState.CompleteTask();**
                    break;
}

This is the dummy method which is only used as a flag.这是仅用作标志的虚拟方法。

public class SessionState 
{
public async Task CompleteTask()
        {
            var tcs = new TaskCompletionSource<bool>();
            tcs.SetResult(true);
            
        }
}

This is how I check if the above method is completed:这是我检查上述方法是否完成的方式:

private async Task ProductQuantityEvaluation()
{
 Task.WhenAny(sessionState.CompleteTask());
}

So, the code is working perfectly with Wait(miliseconds) but when I want to wait for completion of another method using WhenAny();因此,代码与 Wait(miliseconds) 完美配合,但是当我想使用 WhenAny(); 等待另一个方法完成时; it does not work.这没用。 What am I missing?我错过了什么?

Currently, your CompleteTask() method just creates a task and completes it:目前,您的CompleteTask()方法只是创建一个任务并完成它:

public async Task CompleteTask()
{
  var tcs = new TaskCompletionSource<bool>();
  tcs.SetResult(true);            
}

So, really, it's just always returning an already completed task.所以,真的,它总是返回一个已经完成的任务。

Also, this code isn't waiting for CompleteTask to be called ;此外,此代码不等待CompleteTask调用 it's calling it and then waiting for the task it returned:它正在调用它,然后等待它返回的任务:

// Removing the unnecessary Task.WhenAny and adding the missing await:
private async Task ProductQuantityEvaluation()
{
  await sessionState.CompleteTask();
}

// Is the same as this:
private async Task ProductQuantityEvaluation()
{
  var task = sessionState.CompleteTask();
  await task;
}

What you're actually looking for is a kind of "asynchronous signal".您实际上正在寻找的是一种“异步信号”。 Which in most cases is a TaskCompletionSource<T> .在大多数情况下是TaskCompletionSource<T> Where you're going wrong is that you're creating it within the notification method;你出错的地方是你在通知方法中创建它; you want to create it before then, have the controller action await the TCS task, and then later trigger it from the notification method.您想在此之前创建它,让 controller 操作await TCS 任务,然后稍后从通知方法触发它。 Something like this:像这样的东西:

public class SessionState 
{
  private readonly TaskCompletionSource<bool> _tcs = new();
  public Task CompleteTask => _tcs.Task;
  public void Complete() => _tcs.TrySetResult(true);
}

Wait for it like this:像这样等待它:

private async Task ProductQuantityEvaluation()
{
  await sessionState.CompleteTask;
}

and trigger it like this:并像这样触发它:

public class EventProcessor 
{
case AppConstants.eventTypeSendActualProductQuantities:
  var disapprovedProductInfo = JsonSerializer.Deserialize<PublishedProductModel>(message);
  sessionState.publishedProductModel = disapprovedProductInfo;
  sessionState.productQuantityStatus = 2;
  sessionState.Complete();
  break;
}

That should get it basically working.这应该让它基本上工作。

Now, there are a couple of problems still with this code, that you'll probably need to fix next.现在,这段代码仍然存在一些问题,您接下来可能需要修复这些问题。

  1. The "set the results and then send a completion signal" is kind of weird. “设置结果然后发送完成信号”有点奇怪。 Why aren't the results themselves part of the completion signal?为什么结果本身不是完成信号的一部分? You have a TaskCompletionSource<T> , and I think it would make sense to have the publishedProductModel and productQuantityStatus as part of that T .你有一个TaskCompletionSource<T> ,我认为将publishedProductModelproductQuantityStatus作为T的一部分是有意义的。
  2. You're probably going to need more than one signal.您可能需要多个信号。 A more normal usage of this kind of pattern is to have a Dictionary<SomeKey, TaskCompletionSource<T>> .这种模式更正常的用法是拥有Dictionary<SomeKey, TaskCompletionSource<T>> Unless you already have a Dictionary<SomeKey, SessionState> elsewhere in your code, and each SessionState represents a single request.除非您在代码的其他地方已经有Dictionary<SomeKey, SessionState> ,并且每个SessionState代表一个请求。 In that case, just having the one TaskCompletionSource per SessionState would be fine.在这种情况下,每个SessionState只需一个TaskCompletionSource就可以了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM