Създаване на асинхронен метод и задаване на резултата от друг метод

+4 гласа
184 прегледа
попитан 2016 юни 28 от Nikoleta.V. (4,090 точки)

Работя с доста специфично Web API, което използва TCP и SSL stream. Връзката с апито е постоянна(не се затваря след всяко четене/писане), защото по нея се получават известия от сървъра. 

Имплементирах следния метод за четене: 

private void Listen() 

    int length; 

    do 

    { 

        var buffer = new byte[TcpClient.ReceiveBufferSize]; 

        length = SslStream.Read(buffer, 0, TcpClient.ReceiveBufferSize); 

        if (length > 0) 

        { 

            // обработка на данните 

            Listen(); 

        } 

    } while (length > 0); 

    // свързване отново 

Сега трябва да извикам някои методи от апито. Искам да поддържат TPL(async/await). Проблемът е, че асинхронните части на тези методи са имплементирани в горния метод. Например: 

public async Task<bool> Authenticate(string clientId, string clientSecret) 

    await SendAuthMessageOverNetwork(clientId, clientSecret); 

    return await ReceiveAuthResponseFromServer(); 

    // Проблемът е че няма метод ReceiveAuthResponseFromServer 

    //отговорът се получава в четящата нишка 

Не съм много запознат с TPL. Реших този проблем като използвах Task,което не прави нищо освен да изчаква сигнал (AutoResetEvent) от четящата нишка: 

private AutoResetEvent _loginEvent; 

private bool _loginResult; 

public async Task<bool> Authenticate(string clientId, string clientSecret) 

    await SendAuthMessageOverNetwork(clientId, clientSecret); 

    return await Task<bool>.Factory.StartNew(() => { _loginEvent.WaitOne(); return _loginResult; }); 

private void Listen() 

    ... 

    if (msg.Type == MessageTypes.AuthenticationResponse) 

    { 

        _loginResult = true; 

        _loginEvent.Set(); 

    } 

    ... 

Но така не ми харесва. Сигурно има и по-прост начин да се направи? Може ли да се направи само с функционалностите на Task, без AutoResetEvent и междинна глобална променлива? 

1 отговор

0 гласа
отговорени 2016 юли 5 от valeri.hristov (7,340 точки)

Прав си. AutoResetEvent не се ползва така.  Ето какво може да направиш:

  • Ако приемеш, че SendAuthMessageOverNetwork се свръзва с ReceiveAuthResponseFromServer, направи ги в един метод
  • В този метод изпрати заявката и сложи нов TaskCompletionSource в опашката, която се вижда от четящия цикъл
  • Върни Task от задачата като резултат
  • В четящия цикъл, когато четеш съобщение, вади следващия елемент от опашката и използвай taskSource.SetResult(responseFromServer)

Нещо такова:

private readonly Queue<ResponseMessageType> _responseQueue = new Queue<ResponseMessageType>();

public async Task<bool> Authenticate(string clientId, string clientSecret) {

    var response = AsyncRequestAResponse(MakeAuthMessage(clientId, clientSecret));

    return (await response).Type == MessageTypes.AuthenticationResponse

}

public Task<bool> AsyncRequestAResponse(RequestMessageType request) {

    var responseSource = new TaskCompletionSource<ResponseMessageType>();

    _responseQueue.Enqueue(responseSource);

    Send(request);

    return responseSource.Task

}

private void Listen() {

    ...

    if (_responseQueue.Count == 0)

        throw new Exception("Erm, why are they responding before we requested anything?");

    _responseQueue.Dequeue().SetResult(msg);

}

С други думи, ползвай TaskCompletionSource<T> да превежда нещата, които правиш вътрешно по мрежата в асинхронните неща, които откриваш за извикване.

Най-вероятно няма да е като горния код. Приех, че има 1 към 1 заявка/отговор. Може да се наложи да свързваш ID на заявката с ID на отговора, да сложиш таймери, които хвърлят изключения вместо резултат в опашката и тн.

Ако отговорите пристигат конкурентно на изпращането на заявките е важно да сложиш lock-ове около операциите,които пипат по опашката.

...