1

我正在实现我的应用程序的网络层,即使用异步 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();      
}

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

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

4

2 回答 2

1

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

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

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

await sendRequest();
await receiveResponse();

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


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

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

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

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

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

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

于 2012-11-26T12:24:52.943 回答
0

行,

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

任务超时扩展:

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());

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

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

于 2012-11-26T22:35:59.880 回答