Как создать оболочку для вызова асинхронного ожидания?
-
14-11-2019 - |
Вопрос
Насколько я могу судить, встроенной поддержки (или расширения фреймворка) для ConnectAsync
/AcceptAsync
/SendAsync
/ReceiveAsync
, и т. д..Как бы мне написать свою собственную оболочку, которая бы поддерживалась механизмом async-await.Например, мой текущий код, который обрабатывает ReceiveAsyn
c как встроенный, так и в обратном вызове (который указан в SocketAsyncEventArgs
):
private void PostReceive(SocketAsyncEventArgs e)
{
e.SetBuffer(ReceiveBuffer.DataBuffer, ReceiveBuffer.Count, ReceiveBuffer.Remaining);
e.Completed += Receive_Completed;
// if ReceiveAsync returns false, then completion happened inline
if (m_RemoteSocket.ReceiveAsync(e) == false)
{
Receive_Completed(this, e);
}
}
.
private void Receive_Completed(object sender, SocketAsyncEventArgs e)
{
e.Completed -= Receive_Completed;
if (e.BytesTransferred == 0 || e.SocketError != SocketError.Success)
{
if (e.BytesTransferred > 0)
{
OnDataReceived(e);
}
Disconnect(e);
return;
}
OnDataReceived(e);
//
// we do not push the SocketAsyncEventArgs back onto the pool, instead
// we reuse it in the next receive call
//
PostReceive(e);
}
Решение
Хитрость заключается в том, чтобы использовать TaskCompletionSource чтобы справиться с этим сценарием.
Я написал об этом в блоге.Подробности см. Подготовка существующего кода для Await.
Другие советы
Вы также можете написать собственный ожидаемый файл, который мне нравится больше в этой ситуации.Это метод Стивена Тауба из Microsoft.Подробнее об этой технике можно прочитать здесь.http://blogs.msdn.com/b/pfxteam/archive/2011/12/15/10248293.aspx
Вот ожидаемый кастом:
public sealed class SocketAwaitable : INotifyCompletion
{
private readonly static Action SENTINEL = () => { };
internal bool m_wasCompleted;
internal Action m_continuation;
internal SocketAsyncEventArgs m_eventArgs;
public SocketAwaitable(SocketAsyncEventArgs eventArgs)
{
if (eventArgs == null) throw new ArgumentNullException("eventArgs");
m_eventArgs = eventArgs;
eventArgs.Completed += delegate
{
var prev = m_continuation ?? Interlocked.CompareExchange(
ref m_continuation, SENTINEL, null);
if (prev != null) prev();
};
}
internal void Reset()
{
m_wasCompleted = false;
m_continuation = null;
}
public SocketAwaitable GetAwaiter() { return this; }
public bool IsCompleted { get { return m_wasCompleted; } }
public void OnCompleted(Action continuation)
{
if (m_continuation == SENTINEL ||
Interlocked.CompareExchange(
ref m_continuation, continuation, null) == SENTINEL)
{
Task.Run(continuation);
}
}
public void GetResult()
{
if (m_eventArgs.SocketError != SocketError.Success)
throw new SocketException((int)m_eventArgs.SocketError);
}
}
Некоторые методы расширения, которые можно добавить в класс сокета и сделать его удобным:
public static class SocketExtensions
{
public static SocketAwaitable ReceiveAsync(this Socket socket,
SocketAwaitable awaitable)
{
awaitable.Reset();
if (!socket.ReceiveAsync(awaitable.m_eventArgs))
awaitable.m_wasCompleted = true;
return awaitable;
}
public static SocketAwaitable SendAsync(this Socket socket,
SocketAwaitable awaitable)
{
awaitable.Reset();
if (!socket.SendAsync(awaitable.m_eventArgs))
awaitable.m_wasCompleted = true;
return awaitable;
}
// ...
}
В использовании:
static async Task ReadAsync(Socket s)
{
// Reusable SocketAsyncEventArgs and awaitable wrapper
var args = new SocketAsyncEventArgs();
args.SetBuffer(new byte[0x1000], 0, 0x1000);
var awaitable = new SocketAwaitable(args);
// Do processing, continually receiving from the socket
while (true)
{
await s.ReceiveAsync(awaitable);
int bytesRead = args.BytesTransferred;
if (bytesRead <= 0) break;
Console.WriteLine(bytesRead);
}
}
Для разъемов есть является оболочка в .NET 4.5.Если вы используете .NET 4, я бы рекомендовал использовать APM, а не асинхронный шаблон на основе событий.Он преобразуется в Task
Гораздо проще.