【问题标题】:WinRT: blocking TaskWinRT:阻止任务
【发布时间】:2023-03-20 08:50:02
【问题描述】:

我正在实现我的应用程序的网络层,即使用异步 JSON-RPC 协议。

为了与服务器通信,我想做一个方法来发送正确的请求,等到服务器发送响应,然后返回它。一切都使用 async/await 关键字。

这里是简化的示例代码:

字符串响应;

Task<string> SendRequest(string methodName, string methodParams)
{
    string request = generateRequest(methodName, methodParams);

    await Send(request); // this will send using DataWriter, and StreamSocket

    // block Task until response arrives

    return response;
}


async void ReceiveLoop()
{
    while (true)
    {
            uint numStrBytes = await _reader.LoadAsync(BufferSize);

            string msg = _reader.ReadString(numStrBytes);

            response = msg;

            // unblock previously blocked SendRequest
        }        
    }
}

async void main()
{
    RecieveLoop();  
}

async void SendButtonPressed()
{
    string response = await SendRequest("Test method", "Test params");

    Debug.WriteLine("Response = " + response);
}

这种模式的主要问题是这种阻塞操作。此操作应阻止当前任务,并允许处理超时。 我尝试使用 ManualResetEvent 和 WaitOne(int) 来处理这个问题,但它会冻结整个线程,并且因为我只使用 async/await,它会冻结整个应用程序(对我来说更准确的是 UI 线程)。

对我来说看起来很老套的解决方案是我可以将 Task.Delay 与 CancellationTokens 一起使用。

看起来像这样:

...
CancellationTokenSource cts;
int timeout = 10000;

Task<string> SendRequest(string methodName, string methodParams)
{
    ... (prepare request, and send)

    cts = new CancellationTokenSource();

    try
    {
        await Task.Delay(timeout, cts.Token);
    } catch(TaskCanceledException)
    {

    }

    // do rest
}

async void ReceiveLoop()
{
    // init recieve loop, and recieve message

    cts.Cancel();      
}

该解决方案的问题(除了看起来像黑客之外)是性能 - 每个请求都会引发异常,需要处理(在这种情况下跳过)。这个很慢,很痛:)

我怎样才能以更优雅的方式做到这一点?是否有任何其他选项可以阻止任务?

【问题讨论】:

    标签: asynchronous windows-runtime task block async-await


    【解决方案1】:

    将接收和发送循环转换为一个由发送按钮触发的循环:

    while(!cancel) {
        await sendRequest();
        await receiveResponse();
    }
    

    不确定您是否甚至需要循环,因为我不知道您的确切要求。它可能看起来像这样:

    await sendRequest();
    await receiveResponse();
    

    这将执行一个请求-响应-循环。


    阅读您下面的评论,我想添加以下内容:您是对的,现在您需要两个循环。我将通过首先创建一个类来表示正在运行的请求来解决这个问题:

    class Request { int ID; object Response; TaskCompletionCource CompletedTask; }
    

    发送内容时,您创建此类的一个实例并将其添加到Dictionary&lt;int, Request&gt;ID 是服务器可以用来响应您的请求的共享标识符(我知道多个请求可能未完成)。

    收到回复后,您保存结果并将CompletedTask 标记为已完成。酸发送方法应如下所示:

    var request = CreateRequest();
    await sendRequest(request);
    await request.CompletedTask.Task;
    return request.Response;
    

    TaskCompletionSource 将作为一个事件。 Request 类封装了所有相关数据。

    【讨论】:

    • 问题是我需要recieve循环,因为这个应用程序将使用双向JSON-RPC,所以服务器可以随时在应用程序上执行远程方法。应用应始终查找传入消息。
    • 我已经添加了我的想法。
    • 好的,感谢这个,但正如我之前提到的,我需要处理超时。有什么办法比检查单独的线程更优雅,哪个 TaskCompletionSource-s 太旧了,然后手动完成它们?
    • 您可以像这样使您的操作超时:blogs.msdn.com/b/pfxteam/archive/2011/11/10/10235834.aspx 这在幕后完成了 TCS。
    • 在我下面的帖子中(有完整的解决方案)我已经提出了一个问题,但它可以被忽略,所以我会在这里再问一遍:“真的,没有其他方法可以阻塞任务?临界区呢,任务没有互斥体吗?”
    【解决方案2】:

    好的,

    在 usr 的帮助下,我设法编写了这段代码,它会阻塞当前任务,超时:

    TaskTimeout 扩展:

    internal struct VoidTypeStruct{}
    
     public static class TaskTimeoutExtension
        {
            public static async Task TimeoutAfter(this Task task, int millisecondsTimeout)
            {
                if (task == await Task.WhenAny(task, Task.Delay(millisecondsTimeout)))
                    await task;
                else
                    throw new TimeoutException();
            }
        }
    

    阻止任务:

    var tcs = new TaskCompletionSource<VoidTypeStruct>();
    
    try
    {
        await Task.Run(async () => { await tcs.Task; }).TimeoutAfter(RequestTimeout);
    }
    catch (TimeoutException)
    {                           
    }
    
    // store tcs variable somewhere
    

    解除阻塞任务:

    tcs.SetResult(new VoidTypeStruct());
    

    这工作得非常快(至少比以前的解决方案好得多)。

    最后一个问题:真的,没有其他方法可以阻止任务吗?关键部分呢,任务没有互斥体吗?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-02
      • 2019-07-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多