Как правильно отложить, когда нет задачи ждать - PullRequest
1 голос
/ 06 октября 2019

У меня есть задача, которая ожидает, чтобы свойство было установлено в true (= выполнено). Я получаю это изменение значения свойства через EventHandler (точнее, System.Diagnostics.Process.OutputDataReceived - он непрерывно читает выходные данные другого процесса, пока не будет предоставлен правильный вывод). Однако проверка собственности все время кажется несколько неэффективной. Я попытался добавить небольшую задержку в один тик, потому что, полагаю, я могу позволить себе такое ожидание, если это сэкономит процессорное время, но я читаю .NET борется с долями миллисекунд. Могу ли я улучшить этот код?

private ConcurrentBag<string> _allMessages = new ConcurrentBag<string>();

public OutputRetriever()
{
    var process = new System.Diagnostics.Process();
    ...
    process.OutputDataReceived += OutputDataReceived;
    process.Start();
}

public async Task<string[]> GetAllOutput()
{   
    while (!IsCompleted)
    {
        // how to properly wait here?
        // await Task.Delay(TimeSpan.FromTicks(1)); // is this ok?
    }
    return _allMessages.ToArray();
}

private void ConsoleDataReceived(object sender, DataReceivedEventArgs e)
{
    _allMessages.Add(e?.Data);
    if (e?.Data == "success")
    {
        IsCompleted = true;
    }
}

1 Ответ

1 голос
/ 06 октября 2019

Таймеры в Windows имеют разрешение прибл. 16 мс, поэтому любая задержка ниже 16 мс не может быть точно достигнута. Это относится к любому таймеру - таймеры .NET - это просто оболочки для собственных таймеров Windows.

Вместо ожидания занятости в цикле создайте пользовательский TaskCompletionSource<T> и верните Task, который можно ожидать.

class OutputRetriever
{
    private readonly ConcurrentBag<string> _allMessages = new ConcurrentBag<string>();

    private readonly TaskCompletionSource<string[]> _taskSource
        = new TaskCompletionSource<string[]>();

    // Note: this method is not async anymore
    public Task<string[]> GetAllOutput()
    {
        // We just return a task that can be awaited
        return _taskSource.Task;
    }

    void ConsoleDataReceived(object sender, DataReceivedEventArgs e)
    {
        _allMessages.Add(e?.Data);
        if (e?.Data == "success")
        {
            // Here we notify that the task is completed by setting the result
            _taskSource.SetResult(_allMessages.ToArray());
        }
    }
}

Теперь клиенты могут просто ожидать результатов какобычно:

var receiver = new OutputReceiver();
string[] messages = await receiver.GetAllOutput();
...