任务继续被安排到非线程池线程。为什么?

时间:2014-04-09 14:09:09

标签: c# multithreading mono async-await

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

当我等待主线程中的任务突然继续(我的例程的剩余部分)被安排到我的工作线程有什么问题,因为我不希望我的线程将被用作随机任务的ThreadPool线程。

仅在使用Mono运行代码时才会遇到此问题。

这是一个代码,它重现了mono上的问题(在ma​​c os x和linux系统上测试):

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

所以,这是MONO输出:

   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;

这是Windows输出:

   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;

请注意(最后一行)上下文捕获的不同之处。

编辑:

使用Mono 3.4.0无法重复,因此似乎是旧版本中的错误(至少3.2.6);

1 个答案:

答案 0 :(得分:0)

我认为你在Mono运行时发现了一个错误。 await之后的延续不应发生在具有与TaskAwaiter捕获的同步上下文不同的线程上await

以下情况是可能的:

  1. 原始线程和完成线程都具有相同的同步上下文。可以内联延续(在完成线程上同步执行)。
  2. 原始线程和完成线程都没有同步上下文(SynchronizationContext.Current == null)。延续仍然可以内联。
  3. 在任何其他组合中,不得内联延续。
  4. 通过&#34;可以内联&#34;我的意思是它不是必需的或保证不是这样(它仍然可以使用TaskScheduler.CurrentTaskScheduler.FromCurrentSynchronizationContext进行异步执行。尽管如此,根据微软当前的TPL实现,它确实被内联到条件#1和#2。

    但是,#3的一定不能内联,这是常识所决定的。所以随时向Xamarin报告错误。首先尝试最新的Mono构建,看看问题是否仍然存在。