Тайм-аут для асинхронной задачи <T>с дополнительной обработкой исключений - PullRequest
2 голосов
/ 03 ноября 2019

В моем проекте я ссылаюсь на типы и интерфейсы из библиотеки динамических ссылок. Самое первое, что я должен сделать при использовании этой конкретной библиотеки, - это создать экземпляр EA.Repository, который определен внутри библиотеки и служит своего рода точкой входа для дальнейшего использования.

Создание экземпляра EA.Repository repository = new EA.Repository() выполняет некоторые сложные вещи в фоновом режиме, и я сталкиваюсь с тремя возможными результатами:

  1. Инстанцирование занимает некоторое время, но заканчивается успешно в конце
  2. Исключение выдается (или сразу, или через некоторое время)
  3. Инстанцирование блокируется навсегда (в этом случае я хотел бы отменить и сообщить пользователю)

Я смог придумать асинхронныйподход с использованием Task:

public static void Connect()
{
    // Do the lengthy instantiation asynchronously
    Task<EA.Repository> task = Task.Run(() => { return new EA.Repository(); });

    bool isCompletedInTime;

    try
    {
        // Timeout after 5.0 seconds
        isCompletedInTime = task.Wait(5000);
    }
    catch (Exception)
    {
        // If the instantiation fails (in time), throw a custom exception
        throw new ConnectionException();
    }

    if (isCompletedInTime)
    {
        // If the instantiation finishes in time, store the object for later
        EapManager.Repository = task.Result;
    }
    else
    {       
        // If the instantiation did not finish in time, throw a custom exception
        throw new TimeoutException();
    }
}

(я знаю, вы, вероятно, уже можете заметить много проблем здесь. Пожалуйста, будьте терпеливы со мной ... Рекомендации будут оценены!)

Этот подход работает до сих пор - я могу смоделировать сценарий «исключения» и «тайм-аут» и получить желаемое поведение.

Однако у меня естьобозначен другой крайний случай: предположим, что задача создания экземпляра занимает достаточно много времени, чтобы истекло время ожидания, а затем выдает исключение. В этом случае я иногда получаю AggregateException, говорящее, что задача не была выполнена.

Я изо всех сил пытаюсь найти реальное решение для этого. Я действительно не могу отменить задачу, когда истекает время ожидания, потому что создание экземпляров блокировки явно мешает мне использовать CancellationToken подход.

Единственное, что я могу придумать, это начать наблюдать за задачей асинхронно (то есть начать другое задание) прямо перед тем, как бросить мой кастомный TimeoutException:

Task observerTask = Task.Run(() => {
    try { task.Wait(); }
    catch (Exception) { }
});

throw new TimeoutException();

Конечно, если создание экземпляров действительно блокирует навсегда, у меня уже было первое задание, которое никогда не закончилось. С заданием наблюдателя теперь у меня даже есть два!

Я совершенно не уверен в этом подходе, поэтому любые советы будут приветствоваться!

Заранее большое спасибо!

Ответы [ 2 ]

2 голосов
/ 03 ноября 2019

Я не уверен, полностью ли я понял, чего вы пытаетесь достичь, но что, если вы сделаете что-то вроде этого -

public static void Connect()
{
    Task<EA.Repository> _realWork = Task.Run(() => { return new EA.Repository(); });
    Task _timeoutTask = Task.Delay(5000);
    Task.WaitAny(new Task[]{_realWork, timeoutTask});
    if (_timeoutTask.Completed)
    {
        // timed out
    }
    else
    {
        // all good, access _realWork.Result
    }
}

или вы даже можете пойти немного короче -

public static void Connect()
{
    Task<EA.Repository> _realWork = Task.Run(() => { return new EA.Repository(); });
    var completedTaskIndex = Task.WaitAny(new Task[]{_realWork}, 5000);
    if (completedTaskIndex == -1)
    {
        // timed out
    }
    else
    {
        // all good, access _realWork.Result
    }
}

Вы также всегда можете позвонить Task.Run с CancellationToken, который истечет время ожидания, но это вызовет исключение - вышеупомянутые решения дают вам контроль над поведением без исключения, будучибросили (хотя вы всегда можете try/catch)

0 голосов
/ 03 ноября 2019

Вот метод расширения, который вы могли бы использовать для явного наблюдения за задачами, которые могут завершиться с ошибкой, пока не замечены:

public static Task<T> AsObserved<T>(this Task<T> task)
{
    task.ContinueWith(t => t.Exception);
    return task;
}

Пример использования:

var task = Task.Run(() => new EA.Repository()).AsObserved();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...