IDisposable и Task.Wait все ждут на себе - PullRequest
0 голосов
/ 10 марта 2019

У меня есть класс, который порождает несколько рабочих задач, используя Task.Run и помещая ссылку на эти задачи в коллекцию.Кроме того, этот класс реализует IDisposable для очистки.В реализации Dispose() я использую Task.WaitAll(_listOfTasks) для ожидания завершения всех рабочих.

Теперь может случиться так, что вызов Dispose() поступает из одной из тех рабочих задач, которая, очевидно, приведет кв тупике, поскольку WaitAll ожидает себя.

Существует ли шаблон или рекомендуемый способ обойти эту ситуацию?Или есть другие способы убедиться, что все выполняющиеся Задачи завершены при удалении класса?

public class Loader : IDisposable
{

    private readonly IList<Task> _runningTasks = new List<Task>();

    public Loader()
    {
    }

    public void Dispose()
    {
        Task.WaitAll(_runningTasks.ToArray());
    } 

    public void StartLoadAsync()
    {
        var task = Task.Run(() => DoSomeWork());

        _runningTasks.Add(task);         
    }

    void DoSomeWork()
    {
        // after doing some actual work here, call Dispose() in certain cases
        if (SomeCondition)
        {
            Dispose();
        }
    }
}

1 Ответ

0 голосов
/ 12 марта 2019

Существует несколько способов использования интерфейса IDisposable в экосистеме .NET.Я хочу показать двум из них, которые особенно актуальны в многопоточном / асинхронном использовании.

Reactive Extensions

При подписке на наблюдаемый объект IDisposable возвращается.Здесь интерфейс служит триггером отмены.Нет привязки к потоку, и он может быть вызван в любое время.Реализация обещает отмену с максимальным усилием, но не дает никаких гарантий, когда отмена наконец произойдет.Это означает, что после вызова Dispose подписка может еще некоторое время оставаться активной.

IAsyncEnumerator

В следующем интерфейсе IAsyncEnumerable вы сможете передать токен отменыпри получении перечислителя.Перечислитель реализует IAsyncDisposable и должен быть утилизирован.Перечислитель не обязательно должен быть потокобезопасным, т. Е. Ему не разрешено вызывать DisposeAsync, пока еще выполняется вызов другого метода интерфейса или возвращенной задачи.Если вы хотите остановить перечисление, вы должны использовать токен отмены.

Заключение

Важно различать отмену и очистку ресурса.В вашем случае вы также можете использовать токен отмены.Поскольку у вас уже есть все, что связано с вызовом метода, вы можете альтернативно добавить метод Cancel.Если вам нужно сейчас, когда ваш загрузчик действительно завершает работу, вы можете реализовать интерфейс IAsyncDisposable:

public void Cancel() => ...;
public async ValueTask DisposeAsync()
{
    // Cancel();
    await Task.WhenAll(_runningTasks.ToArray());
}

Если хотите, вы можете вызвать Cancel из метода DisposeAsync.Я не уверен, есть ли лучшая практика по этому поводу.Я бы не стал звонить туда Cancel, потому что это дает вам или вашим пользователям API больше возможностей.Обратите внимание, что интерфейс IAsyncDisposable будет поставляться только с Netstandard 2.1.Это не должно помешать вам, однако, использовать этот шаблон прямо сейчас.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...