Как заставить ActionBlock завершить быстро - PullRequest
0 голосов
/ 10 октября 2019

В соответствии с документацией :

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

Такое поведение не идеально в моем случае. Я хочу иметь возможность отменить работу в любое время, но обработка каждого отдельного действия занимает много времени. Поэтому, когда я отменяю токен, эффект не мгновенный. Я должен дождаться завершения обработанного в данный момент элемента. У меня нет возможности отменить действия напрямую, потому что API, который я использую, не может быть отменен. Могу ли я сделать что-нибудь, чтобы блок игнорировал текущее действие и завершил его немедленно?

Вот пример, демонстрирующий мою проблему. Токен отменяется через 500 мс, а продолжительность каждого действия - 1000 мсек:

static async Task Main()
{
    var cts = new CancellationTokenSource(500);
    var block = new ActionBlock<int>(async x =>
    {
        await Task.Delay(1000);
    }, new ExecutionDataflowBlockOptions() { CancellationToken = cts.Token });
    block.Post(1); // I must wait for this one to complete
    block.Post(2); // This one is ignored
    block.Complete();
    var stopwatch = Stopwatch.StartNew();
    try
    {
        await block.Completion;
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine($"Canceled after {stopwatch.ElapsedMilliseconds} msec");
    }
}

Выход:

Отменяется через 1035 мс

Требуемый выход будет отменен через ~ 500 мсек.

1 Ответ

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

Основываясь на этом отрывке из вашего комментария ...:

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

... и предполагая, что вы действительно в порядке, оставив задание выполненным, выможно просто обернуть задание, которое вы хотите вызвать , в другое Task, которое будет постоянно запрашивать отмену или завершение, и вместо этого , что Task. Взгляните на следующий код «проверки концепции», который оборачивает «долго выполняющуюся» задачу в другую задачу, «выполняющую задачи», с постоянным опросом завернутой задачи на предмет завершения, и CancellationToken для отмены (полностью «побуждающий»). «текущий момент», вы, конечно, захотите немного изменить его):

public class LongRunningTaskSource
{
    public Task LongRunning(int milliseconds)
    {
        return Task.Run(() =>
        {
            Console.WriteLine("Starting long running task");
            Thread.Sleep(3000);
            Console.WriteLine("Finished long running task");
        });
    }

    public Task LongRunningTaskWrapper(int milliseconds, CancellationToken token)
    {
        Task task = LongRunning(milliseconds);

        Task wrapperTask = Task.Run(() =>
        {
            while (true)
            {
                //Check for completion (you could, of course, do different things
                //depending on whether it is faulted or completed).
                if (!(task.Status == TaskStatus.Running))
                    break;

                //Check for cancellation.
                if (token.IsCancellationRequested)
                {
                    Console.WriteLine("Aborting Task.");

                    token.ThrowIfCancellationRequested();
                }
            }

        }, token);

        return wrapperTask;
    }
}

Используя следующий код:

static void Main()
{
    LongRunningTaskSource longRunning = new LongRunningTaskSource();

    CancellationTokenSource cts = new CancellationTokenSource(1500);

    Task task = longRunning.LongRunningTaskWrapper(3000, cts.Token);

    //Sleep long enough to let things roll on their own.
    Thread.Sleep(5000);

    Console.WriteLine("Ended Main");
}

... производит следующий вывод:

Starting long running task
Aborting Task.
Exception thrown: 'System.OperationCanceledException' in mscorlib.dll
Finished long running task
Ended Main

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

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

static void Main()
{
    LongRunningTaskSource longRunning = new LongRunningTaskSource();

    CancellationTokenSource cts = new CancellationTokenSource(3000);

    Task task = longRunning.LongRunningTaskWrapper(1500, cts.Token);

    //Sleep long enough to let things roll on their own.
    Thread.Sleep(5000);

    Console.WriteLine("Ended Main");
}

... производит следующий вывод:

Starting long running task
Finished long running task
Ended Main

Таким образом, задача запускалась и заканчивалась до истечения времени ожидания иничто не должно было быть отменено. Конечно, ничто не блокируется во время ожидания. Как вы, наверное, уже знаете, конечно, если вы знаете , что используется за кулисами в долговременном коде, было бы хорошо очистить при необходимости.

Надеюсь,Вы можете адаптировать этот пример, чтобы передать что-то подобное в свой ActionBlock.

Отказ от ответственности и примечания

Я не знаком с библиотекой потока данных TPL, так что это, конечно, обходной путь. Кроме того, если все, что у вас есть, например, синхронный вызов метода, на который вы вообще не имеете никакого влияния, тогда вам, очевидно, понадобятся две задачи. Одна задача-обертка, чтобы обернуть синхронный вызов, и другая, чтобы обернуть задачу-обертку, чтобы включить непрерывный опрос статуса и проверки отмены.

...