如何在执行异步任务时成功返回 API 调用?

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

C# How do I have an API call successfully return while executing an Async task?

问题

我在编写一个异步调用的API时遇到了问题。
背景是,我们有一个需要更新订阅者的调用,涉及到API调用中的一些细节。订阅者调用本身相对较慢,不返回任何内容,也没有理由等待它完成。

因此,我试图做的改变是将其异步调用,并让API退出该方法,以便订阅者可以在自己的时间内得到通知,而不会阻塞API调用。

订阅者类已经存在(非异步)方法,我只是试图在其周围包装一个包装器。

以下是一个示例:

public class Subscriber
{
  // 现有方法
  public void NotifySubscriber(string message)
  {
    // 在这里编写代码以向订阅者发送消息
    // 这可能需要很多秒钟才能完成
  }

  // 我的新异步包装器
  public async void NotifySubscriberAsync(string message)
  {
    await Task.Run(() =>
    {
      NotifySubscriber(message);
    });
  }
}

以下是我的API方法示例,该方法从控制器中调用:

public class MyClassService
{
  public void DoSomething(string input)
  {  
    // 执行特定于服务的操作

    Subscriber s = new Subscriber();
    // 旧代码 - 用户被迫等待订阅通知
    //s.NotifySubscriber("my message");

    // 新代码。目标是控制器不再必须等待通知完成
    s.NotifySubscriberAsync("my message");
  }
}

如果我在代码中设置断点,我可以看到API方法正在完成,控制器中的调用代码也是如此。控制器本身会返回一个响应,订阅者调用在后台进行。

然而,问题是前端代码似乎仍然挂起,几乎就像它仍在等待异步方法完成一样。它完全锁死,而控制台窗口中没有错误。

删除异步调用会修复此问题,所以我知道问题与此有关。

我在这里忽略了什么?

英文:

I am having trouble coding an API call to perform an async call.
As a background, we have a call that needs to update a subscriber regarding some of the details within the API call. The subscriber call itself is relatively slow, it does not return anything, and there is no reason that we need to wait for it to complete.

Therefore the change I am trying to do is to have it called async and let the API exit out of the method so that the subscriber can be notified in its own time without holding the API call hostage.

The subscriber class already has existing (non async) methods, and I am simply trying to put a wrapper around it.

Here is an example:

public class Subscriber
{
  // existing method
  public void NotifySubscriber(string message)
  {
    // code goes here to send a message to the subscriber
    // this may take quite a few seconds to complete
  }

  // my new async wrapper
  public async void NotifySubscriberAsync(string message)
  {
    await Task.Run(() =>
    {
      NotifySubscriber(message);
    });
  }
}

And here is an example of my API method, which is called from the controller

public class MyClassService
{
  public void DoSomething(string input)
  {  
    // do operations specific to the service

    Subscriber s = new Subscriber();
    // old code - user forced to wait for the subscription notification
    //s.NotifySubscriber("my message");

    // new code. The goal is for the controller to no longer 
    // have to wait for the notification to complete
    s.NotifySubscriberAsync("my message");
  }
}

If I put a breakpoint in the code, I can see that the API method is running to completion, as is the calling code in the controller. The controller itself is returning a response, with the subscriber call happening in the background.

However, what happens is that the front-end code ends up hanging, almost like it is still waiting for the async method to complete. It completely locks up and with no errors in the console window in dev tools.

Removing the async call fixes it, so I know that the problem is related to that.

What am I overlooking here?

Many thanks and best regards

答案1

得分: 3

你实际问题的解决方案(在用户无需等待的情况下在后台进行通知)并不是一个异步调用。它可能会起作用,但它有许多陷阱,因为你已经发送了响应,而代码仍在运行。

最简单的例子,如果出现错误怎么办?如果发送通知失败了怎么办?你甚至无法通知用户发生了错误,因为他们已经从你的API那里得到了“ok”,并且正在愉快地浏览其他地方。或者使用块。或者中间件。当你拆分任务并且不等待它们时,所有这些都将成为空谈。你的数据库上下文是否仍然有效,还是在调用和范围完成后,依赖注入将其处理掉了?

你想要做的是将发送通知的信息放入表格(或消息队列)中。然后有一个完全不同于你的API的可执行文件,查询该存储并发送通知。

换句话说,将“动作”(“我命令你发送X”)与“执行”(发送X)分开。你绝对可以让你的API在接到命令后说“先生,是的,先生!”但让它是顺序的。说“先生,是的,先生!”然后,作为一个不同的步骤,执行它。不要试图在敬礼的同时做X。那是疯狂的。

英文:

The solution for your actual problem (having notifications in the background without the user waiting) is not an async call. It may work, but it has so many pitfalls, because you already send the response, while there still is code running.

Simplest example, what about errors? What if sending the notification does not work? You cannot even notify the user of the error, they got the "ok" from your API and are happily browsing elsewhere. Or using blocks. Or Middleware. That all goes out the window when you split off tasks and don't await them. Is your database context still valid, or did the dependency injection dispose it after your call and scope was done?

What you want to do is put the information that a notification is to be sent into a table (or message queue). And then have a service, a completely different executable from your API, query that storage and send out notifications.

In other words, split the "action" ("I command you to send X") from the "execution" (sending X). You can absolutely make your API say "Sir, yes, sir!" after receiving the order, but make it sequential. Say "Sir, yes, sir!" and then, as a different step, execute it. Don't try to do X while you salute. That is madness.

答案2

得分: 0

请注意,异步方法不在自己的线程中运行。编译器只是应用了一些魔法,使其表现得像异步执行一样。

我会假设您的示例中异步方法内部不包含其他代码吗?(如果没有等待的话,它将在主线程中运行)。

对于“Fire and Forget”,我只会启动一个线程来执行操作。不需要更多的代码,可以保证无卡顿。

public void NotifySubscriberThreaded(string message)
{
  (new Thread(() =>
  {
    NotifySubscriber(message);
  })).Start();
}

或者更好的是返回线程对象,然后稍后如果需要可以检查它的状态(或加入、终止等)。

public Thread NotifySubscriberThreaded(string message)
{
  Thread t = new Thread(() =>
  {
    NotifySubscriber(message);
  });
  t.Start();
  return t;
}
英文:

Keep in mind, that async methods do not run in an own thread. The compiler just applies some magic, to have it behave like a async execution.

I would assume, that your example does not contain other code IN the async method? (That would be run in the main-thread, if it's not awaited).

For Fire and Forget I would just launch a thread to execute the action. Not more code and works guaranteed without stucks.

  public void NotifySubscriberThreaded(string message)
  {
    (new Thread(() => {
      NotifySubscriber(message);
    })).start();
  }

Or better return the thread object, then you could even check it's status later on if required. (or join, kill, etc.)

  public Thread NotifySubscriberThreaded(string message)
  {
    Thread t = new Thread(() => {
      NotifySubscriber(message);
    });
    t.start();
    return t;
  }

答案3

得分: 0

要使用"Fire And Forget",请执行以下操作:

public void NotifySubscriberAsync(string message)
{
 //fireAndForget
 Task.Run(() =>
 {
   NotifySubscriber(message);
 });
}

如果在内部发生异常,使用async void会导致程序崩溃。而使用Task.Run则会将其视为分离的,然后继续执行。

英文:

To use Fire And Forget, do this:

public void NotifySubscriberAsync(string message)
{
 //fireAndForget
 Task.Run(() =>
 {
   NotifySubscriber(message);
 });
}

Using async void will crash the program if an exception happens inside. Versus Task.Run that will just see it is detached, and continue on.

huangapple
  • 本文由 发表于 2023年6月19日 14:12:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/76504018.html
匿名

发表评论

匿名网友

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

确定