簡體   English   中英

如何強制 ActionBlock 快速完成

[英]How to force an ActionBlock to complete fast

根據文檔

當數據流塊當前未處理消息並且已保證不再處理任何消息時,數據流塊被視為已完成。

在我的情況下,這種行為並不理想。 我希望能夠隨時取消作業,但是每個單獨操作的處理需要很長時間。 因此,當我取消令牌時,效果不會立即生效。 我必須等待當前處理的項目完成。 我無法直接取消操作,因為我使用的 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");
    }
}

Output:

1035 毫秒后取消

所需的 output 將在約 500 毫秒后取消。

根據您的評論摘錄...:

在取消請求的情況下我想要發生的是忽略當前正在運行的工作項。 我已經不在乎了,為什么還要等呢?

...並假設您確實可以讓任務運行,您可以簡單地將您希望調用的作業包裝在另一個Task中,該任務將不斷輪詢取消或完成,然后取消Task 看一下下面的“概念驗證”代碼,它將“長時間運行”的任務包裝在另一個“任務”任務中,不斷輪詢包裝的任務以完成,並使用 CancellationToken 取消(完全“刺激” the-moment”狀態,你當然會想要重新調整它):

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");
}

...產生以下 output:

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");
}

...產生以下 output:

Starting long running task
Finished long running task
Ended Main

因此,任務在超時之前開始並完成,無需取消任何內容。 當然,等待時沒有任何阻塞。 當然,正如您可能已經知道的那樣,如果您知道在長期運行的代碼中幕后使用了什么,那么在必要時最好進行清理。

希望您可以修改此示例以將類似的內容傳遞給您的 ActionBlock。

免責聲明和注意事項

我不熟悉 TPL Dataflow 庫,所以這當然只是一種解決方法。 此外,如果您擁有的只是一個同步方法調用,而您根本沒有任何影響,那么您顯然需要兩個任務。 一個包裝器任務用於包裝同步調用,另一個用於包裝包裝器任務以包括連續狀態輪詢和取消檢查。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM