我正在实现我的应用程序的网络层,即使用异步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)来处理这个问题,但是它会冻结整个Thread,因为我只使用async / await,它会冻结整个应用程序(UI Thread对我更精确)。
对我来说看起来很讨厌的解决方案是我可以将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();
}
该解决方案的问题(除了它看起来像一个黑客)是性能 - 每个请求都有抛出的异常,需要处理(在这种情况下跳过)。这个很慢,很疼:)
我怎样才能以更优雅的方式做到这一点?是否还有其他选项来阻止任务?
答案 0 :(得分: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
类封装了所有相关数据。
答案 1 :(得分:0)
确定,
在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());
这非常快(至少比之前的解决方案好得多)。
最后的问题:真的,有没有其他方法来阻止任务?关键部分怎么样,没有任何Mutexes for Tasks?