在我的控制台应用程序中,我创建了自己的Thread来实现工作队列。此外,我已经为这个唯一的线程实现了我自己的SynchronizationContext。
当我等待主线程中的任务突然继续(我的例程的剩余部分)被安排到我的工作线程有什么问题,因为我不希望我的线程将被用作随机任务的ThreadPool线程。
仅在使用Mono运行代码时才会遇到此问题。
这是一个代码,它重现了mono上的问题(在mac 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);
答案 0 :(得分:0)
我认为你在Mono运行时发现了一个错误。 await
之后的延续不应发生在具有与TaskAwaiter
捕获的同步上下文不同的线程上await
。
以下情况是可能的:
SynchronizationContext.Current == null
)。延续仍然可以内联。通过&#34;可以内联&#34;我的意思是它不是必需的或保证不是这样(它仍然可以使用TaskScheduler.Current
或TaskScheduler.FromCurrentSynchronizationContext
进行异步执行。尽管如此,根据微软当前的TPL实现,它确实被内联到条件#1和#2。
但是,#3的一定不能内联,这是常识所决定的。所以随时向Xamarin报告错误。首先尝试最新的Mono构建,看看问题是否仍然存在。