任务延续原定非线程池中的线程。为什么? [英] Task continuation was scheduled to non thread-pool thread. Why?

查看:149
本文介绍了任务延续原定非线程池中的线程。为什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的控制台应用程序我创建我自己的线程来实现工作队列。此外,我已经实现了我自己的SynchronizationContext为这只线程。

In my console application I do create my own Thread to implement working queue. Besides I have implemented my own SynchronizationContext for this only thread.

当我在等待着从主线程突然延续(我的日常的剩余部分)定于我的工作线程,因为我不希望我的线程将被用作一个线程池线程随机任务什么是错的任务。

When I await a Task from the Main thread suddenly continuation (the remaining part of my routine) is scheduled on to my working thread what is wrong because I do not expect my thread will be used as a ThreadPool thread for random tasks.

我与单声道运行code时,只遇到此问题。

I am experiencing this behaviour only when running the code with Mono.

下面是code抄录单上的问题(在Mac OS X和Linux系统测试):

Here is a code which reproduces the problem on mono (tested at mac os x and linux system):

using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static void Main( string[] args )
    {
        Foo();
        Console.ReadLine();
    }


    async static void Foo()
    {
        Console.WriteLine( "{0}: current thread ID={1}; scheduler={2}; context={3};",
            "   Main BEFORE awaiting",
            Thread.CurrentThread.ManagedThreadId, 
            TaskScheduler.Current.Id,
            SynchronizationContext.Current != null );
            // MONO Output: Main BEFORE awaiting: current thread ID=1; scheduler=1; context=False;

        WorkQueue queue = new WorkQueue();

        // !!! 
        // I do expect that current context which is null will be captured for continuation.
        // !!!
        await queue.Enqueue();

        // !!!
        // As we can see our custom context was captured to continue with this part of code.
        // 
        Console.WriteLine( "{0}: current thread ID={1}; scheduler={2}; context={3};",
            "   Main AFTER awaiting",
            Thread.CurrentThread.ManagedThreadId,
            TaskScheduler.Current.Id,
            SynchronizationContext.Current != null );
        // MONO Output: Main AFTER awaiting: current thread ID=4; scheduler=1; context=True;
    }
}

// Custom context which does nothing but enqueues fake tasks to the queue.
//
class WorkQueueSyncContext : SynchronizationContext
{
    readonly WorkQueue queue;

    public WorkQueueSyncContext( WorkQueue queue )
    {
        this.queue = queue;
    }

    public override void Post( SendOrPostCallback d, object state )
    {
    }

    public override void Send( SendOrPostCallback d, object state )
    {
        queue.Enqueue().Wait();
    }
}

// The queue
//
class WorkQueue
{
    readonly Thread thread;

    class WorkQueueItem
    {
        public TaskCompletionSource<object> Completion
        {
            get;
            set;
        }
    }

    BlockingCollection<WorkQueueItem> queue = new BlockingCollection<WorkQueueItem>();


    public WorkQueue()
    {
        thread = new Thread( new ThreadStart( Run ) );
        thread.Start();
    }

    private void Run()
    {
        // Set ower own SynchronizationContext.
        //
        SynchronizationContext.SetSynchronizationContext( new WorkQueueSyncContext( this ) );

        Console.WriteLine( "{0}: current thread ID={1}; scheduler={2}; context={3};",
            "   WorkQueue START",
            Thread.CurrentThread.ManagedThreadId,
            TaskScheduler.Current.Id,
            SynchronizationContext.Current != null );
        // MONO Output: current thread ID=4; scheduler=1; context=True;

        // Working loop.
        //
        while ( true )
        {
            WorkQueueItem item = queue.Take();

            Console.WriteLine( "{0}: current thread ID={1}; scheduler={2}; context={3};",
                "   WorkQueue DOING TASK",
                Thread.CurrentThread.ManagedThreadId,
                TaskScheduler.Current.Id,
                SynchronizationContext.Current != null );
            // MONO Output: current thread ID=4; scheduler=1; context=True;

            // Completed the task :)
            //
            item.Completion.SetResult( true );
        }
    }

    public Task<object> Enqueue()
    {
        TaskCompletionSource<object> completion = new TaskCompletionSource<object>();
        queue.Add( new WorkQueueItem() { Completion = completion } );
        return completion.Task;
    }
}

所以,这里是单声道输出:

So, here is MONO output:

   Main BEFORE awaiting: current thread ID=1; scheduler=1; context=False;
   WorkQueue START: current thread ID=3; scheduler=1; context=True;
   WorkQueue DOING TASK: current thread ID=3; scheduler=1; context=True;
   Main AFTER awaiting: current thread ID=3; scheduler=1; context=True;

这是视窗输出:

   Main BEFORE awaiting: current thread ID=10; scheduler=1; context=False;
   WorkQueue START: current thread ID=11; scheduler=1; context=True;
   WorkQueue DOING TASK: current thread ID=11; scheduler=1; context=True;
   Main AFTER awaiting: current thread ID=6; scheduler=1; context=False;

请注意(最后一行)如何捕捉环境不同。

Please note (the last line) how context capturing differs.

编辑:

是不可再生使用Mono 3.4.0,这样似乎是在旧版本(至少3.2.6)中的错误;

Is not reproducible with Mono 3.4.0, so seems to be a bug in older version (at least 3.2.6);

推荐答案

我想你已经发现,在单声道运行时的错误。在等待不应该在一个线程发生与通过 TaskAwaiter 捕捉不同的同步上下文,在延续点等待

I think you've found a bug in Mono runtime. The continuation after await should not be happening on a thread with a different synchronization context from that captured by TaskAwaiter, at the point of await.

以下方案是可能的:


  1. 无论是原来的线程和线程完成必须的相同的同步上下文。延续可能被内联(完成线程同步执行)。

  2. 无论是原来的线程和线程完成不同步的情况下( SynchronizationContext.Current == NULL )。延续仍有可能被内联。

  3. 在任何其他组合,延续一定不能被内联。

  1. Both the original thread and the completion thread have the same synchronization context. The continuation may be inlined (executed synchronously on the completion thread).
  2. Both the original thread and the completion thread have no synchronization context (SynchronizationContext.Current == null). The continuation still may be inlined.
  3. In any other combination, the continuation must not be inlined.

通过可能是内联的我的意思是,不要求或保证是如此(这可能仍然使用预定 TaskScheduler.Current TaskScheduler.FromCurrentSynchronizationContext 异步执行)。尽管如此,目前的Microsoft的实现TPL下,它确实得到内联为条件#1和#2。

By "may be inlined" I mean it is not required or guaranteed to be so (it still might be scheduled using TaskScheduler.Current or TaskScheduler.FromCurrentSynchronizationContext for asynchronous execution). Nevertheless, under the current Microsoft's implementation of TPL, it does get inlined for conditions #1 and #2.

然而,作为#3它不能被内联,这是按常理决定。所以随意一个bug报告Xamarin。可以试试最近单建先来看看问题是否仍然存在。

However, for #3 it must not be inlined, this is dictated by common sense. So feel free to report a bug to Xamarin. Try the most recent Mono build first to see if the problem is still there.

这篇关于任务延续原定非线程池中的线程。为什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆