Using Task.Yield to overcome ThreadPool starvation while implementing producer/consumer pattern

后端 未结 2 1360
抹茶落季
抹茶落季 2021-01-13 03:00

Answering the question: Task.Yield - real usages? I proposed to use Task.Yield allowing a pool thread to be reused by other tasks. In such pattern:

  Cancell         


        
2条回答
  •  一整个雨季
    2021-01-13 03:50

    After a bit of debating on the issue with other users - who are worried about the context switching and its influence on the performance. I see what they are worried about.

    But I meant: do something ... inside the loop to be a substantial task - usually in the form of a message handler which reads a message from the queue and processes it. The message handlers are usually user defined and the message bus executes them using some sort of dispatcher. The user can implement a handler which executes synchronously (nobody knows what the user will do), and without Task.Yield that will block the thread to process those synchronous tasks in a loop.

    Not to be empty worded i added tests to github: https://github.com/BBGONE/TestThreadAffinity They compare the ThreadAffinityTaskScheduler, .NET ThreadScheduler with BlockingCollection and .NET ThreadScheduler with Threading.Channels.

    The tests show that for Ultra Short jobs the performance degradation is around 15%. To use the Task.Yield without the performance degradation (even small) - it is not to use extremely short tasks and if the task is too short then combine shorter tasks into a bigger batch.

    [The price of context switch] = [context switch duration] / ([job duration]+[context switch duration]).

    In that case the influence of the switching the tasks is negligible on the performance. But it adds a better task cooperation and responsiveness of the system.

    For long running tasks it is better to use a custom Scheduler which executes tasks on its own dedicated thread pool - (like the WorkStealingTaskScheduler).

    For the mixed jobs - which can contain different parts - short running CPU bound, asynchronous and long running code parts. It is better to split the task into subtasks.

    private async Task HandleLongRunMessage(TestMessage message, CancellationToken token = default(CancellationToken))
    { 
                // SHORT SYNCHRONOUS TASK - execute as is on the default thread (from thread pool)
                CPU_TASK(message, 50);
                // IO BOUND ASYNCH TASK - used as is
                await Task.Delay(50);
                // BUT WRAP the LONG SYNCHRONOUS TASK inside the Task 
                // which is scheduled on the custom thread pool 
                // (to save threadpool threads)
                await Task.Factory.StartNew(() => {
                    CPU_TASK(message, 100000);
                }, token, TaskCreationOptions.DenyChildAttach, _workStealingTaskScheduler);
    }
    

提交回复
热议问题