【问题标题】:Wait for Async function before returning在返回之前等待 Async 函数
【发布时间】:2020-04-09 22:59:24
【问题描述】:

我正在创建一个消息传递系统,但我遇到了问题。在返回 Publish 函数之前,我需要发布消息并等待响应。

这就是我的函数的样子

public async Task<bool> Publish(int ClientId, string msg){
    ...
    // Wait and check if the client the message was sent to respond
    // if that does not happen within 5 seconds, return false, else true
}

private async Task MessageIntercept(int ClientId, string msg){
    // Intercepts all messages
    ...
}

这两个函数都在服务器上,并且只要发送消息(包括使用 Publish 方法发送的消息),MessageIntercept 任务就会自动运行。我可以通过调用上面提到的服务器项目的发布功能从我的 asp.net 网站项目发送消息

基本上我想做的是调用bool Success = Publish(1,"This is a test") 并能够确定消息是否成功发送,客户端是否理解并在 5 秒内恢复消息。

这是一步一步发生的:

  1. 我用Publish从服务器向设备发送了一条消息
  2. 消息被MessageIntercept方法截获(这个我不是很在意,但是代码是这样写的,所有消息都被截获了)
  3. 客户端接收并处理消息
  4. 客户端响应,消息在MessageIntercept被截获,这是我想在返回Publish方法之前验证消息的地方

示例消息;

服务器消息:

{
    ClientId: 13,
    msg: "Hello World"
}

客户响应:

{
    ClientId: 13,
    msg: "{Success: true}"
}

MessageIntercept 拦截所有消息,包括刚刚发送的请求,由于它是请求而不是响应,因此应该忽略它。但是,一旦客户端以消息响应,我想告诉 Publish 方法响应已成功完成,然后返回 true。否则,如果客户端在 5 秒内没有响应,则应假定为 false。

【问题讨论】:

  • MessageIntercept 在这里的重要性是什么?我假设“发布”发出服务器请求,所以整个逻辑都是从发布的角度来看的。
  • @Erndob MessageIntercept 是必需的,因为来自客户端的消息不是由发布方法运行的
  • MessageIntercept方法是怎么调用的?期待吗?

标签: c# asp.net asynchronous message-queue messaging


【解决方案1】:

Publish 无需等待,因此您需要添加一个通知挂钩。像“OnMessageIntercept”这样的事件对我来说很有意义。

然后您可以等待将通过调用通知挂钩来完成的任务。

public async Task<bool> PublishAsync(int clientId, string msg)
{
    // Wait and check if the client the message was sent to respond
    // if that does not happen within 5 seconds, return false, else true
    var messageRecievedSource = new TaskCompletionSource<int>();
    void intercept(object sender, MessageInterceptEventArgs args)
    {
        if (args.ClientId == clientId)
            messageRecievedSource.SetResult(clientId);
    }
    OnMessageIntercept += intercept;
    // EDIT
    // var completed = Task.WaitAny(Task.Delay(TimeSpan.FromSeconds(5)), messageRecievedSource.Task) > 0;
    var completed = await Task.WhenAny(Task.Delay(TimeSpan.FromSeconds(5)), messageRecievedSource.Task);
    OnMessageIntercept -= intercept;

    // EDIT
    // return completed;
    return completed == messageRecievedSource.Task;
}

event EventHandler<MessageInterceptEventArgs> OnMessageIntercept;

private async Task MessageIntercept(int clientId, string msg)
{
    OnMessageIntercept?.Invoke(this, new MessageInterceptEventArgs(clientId, msg));
    // Intercepts all messages
}

class MessageInterceptEventArgs
{
    public MessageInterceptEventArgs(int clientId, string msg)
    {
        ClientId = clientId;
        Msg = msg ?? throw new ArgumentNullException(nameof(msg));
    }

    public int ClientId { get; }
    public string Msg { get; }
}

【讨论】:

  • 有没有办法让这段代码异步,允许一次发送多条消息?
  • 是的,对不起。当您询问“在返回 Publish 函数之前”时,我误解了您希望 Publish 等到它确认响应后再返回。看看我的编辑是否如你所愿。
  • 感谢您的解决方案!老实说,长期以来一直坚持这一点!
【解决方案2】:

如果要同步运行异步函数,可以使用System.Threading.Tasks 库中的以下代码

Task.Run(async () =&gt; await { async method here }).Result

这将导致您的异步方法成为阻塞,并且您的代码在您得到响应之前不会继续。

在您的异步方法中,您可以添加超时 Thread.Sleep(5000) 或在异步函数中迭代结果(如果您避免使用 Thread.Sleep,可以使用秒表)。

【讨论】:

    【解决方案3】:

    您可以使用这样的辅助方法:

    public static async Task<bool> WaitFor(Task task, TimeSpan timeout)
    {
        return await Task.WhenAny(task, Task.Delay(timeout)) == task;
    }
    

    示例用法:

    using System;
    using System.Threading.Tasks;
    
    namespace Demo
    {
        public class Program
        {
            public static async Task Main()
            {
                if (await WaitFor(MyAsyncMethod(), TimeSpan.FromSeconds(1)))
                    Console.WriteLine("First await completed");
                else
                    Console.WriteLine("First await didn't complete");
    
                if (await WaitFor(MyAsyncMethod(), TimeSpan.FromSeconds(3)))
                    Console.WriteLine("Second await completed");
                else
                    Console.WriteLine("Second await didn't complete");
            }
    
            public static async Task MyAsyncMethod()
            {
                await Task.Delay(2000);
            }
    
            public static async Task<bool> WaitFor(Task task, TimeSpan timeout)
            {
                return await Task.WhenAny(task, Task.Delay(timeout)) == task;
            }
        }
    }
    

    对于您的Publish() 方法,调用可能如下所示:

    if (await WaitFor(Publish(1, "msg"), TimeSpan.FromSeconds(5)))
        ...
    

    但是,请注意,使用这种方法的缺点是,如果超时,则不会观察到任务抛出的任何异常。

    如果您需要处理放弃等待任务后可能发生的任何异常,您可以像这样传递异常处理委托:

    public static async Task<bool> WaitFor(Task task, TimeSpan timeout, Action<Exception> handleException)
    {
        var wrapperTask = task.ContinueWith(
            t => handleException(t.Exception.InnerException), 
            TaskContinuationOptions.OnlyOnFaulted);
    
        return await Task.WhenAny(wrapperTask, Task.Delay(timeout)) == task;
    }
    

    那么你可以这样称呼它:

    public static async Task Main()
    {
        if (await WaitFor(
            MyAsyncMethod(), 
            TimeSpan.FromSeconds(1),
            exception => Console.WriteLine("Exception: " + exception.Message))
        )
            Console.WriteLine("First await completed");
        else
            Console.WriteLine("First await didn't complete");
    
        Console.ReadLine();
    }
    

    【讨论】:

    • 这个逻辑不能只在发布方法里面吗?
    • @Erndob 是的,当然 - 但如果您多次使用此逻辑,最好将其封装在辅助方法中。它也不会违反 SRP。
    • @MatthewWatson,我将如何使用WaitFor 方法来验证并等待MessageIntercept 中的客户端响应?
    • @MatthewWatson,对不起,知道已经有一段时间了,再次阅读您的代码示例后,我了解到在您的示例中您正在等待 Publish 方法,但是我该做什么是等待 MessageIntercept 完成,这可能会或可能不会运行,这应该在返回 Publish 方法之前完成,因为我需要验证消息是否已在客户端成功接收和处理。我该怎么做?
    • @Steinar 这个问题有点难以回答,因为不清楚控制流到底是什么。您可以使用WaitFor() 拨打MessageIntercept() 吗?您是否可以编写一个可编译的控制台应用程序来演示您的意思?
    【解决方案4】:

    这是一个异步函数,如果返回结果,您将不得不做一些“事情”。例如,您可以填写隐藏字段值并再次从代码隐藏中读取它。或者触发另一个异步函数并触发代码隐藏方法。 为了能够在回复后写一些东西,你必须听它。

    【讨论】:

      【解决方案5】:
      bool Success = await Publish(1,"This is a test")
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-08-16
        • 1970-01-01
        • 2021-12-02
        • 2017-05-06
        • 1970-01-01
        • 2013-12-08
        相关资源
        最近更新 更多