[英]Task synchronization without a UI thread
在下面的代码中,我想同步任务列表结果的报告。 这现在有效,因为 task.Result 阻塞,直到任务完成。 但是,任务 id = 3 需要很长时间才能完成,并阻止所有其他已完成的任务报告其状态。
我认为我可以通过将报告(Console.Write)移动到 a.ContinueWith 指令中来做到这一点,但我没有 UI 线程,那么如何让 TaskScheduler 同步 the.ContinueWith 任务?
我现在拥有的:
static void Main(string[] args)
{
Console.WriteLine("Starting on {0}", Thread.CurrentThread.ManagedThreadId);
var tasks = new List<Task<int>>();
for (var i = 0; i < 10; i++)
{
var num = i;
var t = Task<int>.Factory.StartNew(() =>
{
if (num == 3)
{
Thread.Sleep(20000);
}
Thread.Sleep(new Random(num).Next(1000, 5000));
Console.WriteLine("Done {0} on {1}", num, Thread.CurrentThread.ManagedThreadId);
return num;
});
tasks.Add(t);
}
foreach (var task in tasks)
{
Console.WriteLine("Completed {0} on {1}", task.Result, Thread.CurrentThread.ManagedThreadId);
}
Console.WriteLine("End of Main");
Console.ReadKey();
}
我想转移到这个或类似的东西,但我需要 Console.Write("Completed...") 都发生在同一个线程上:
static void Main(string[] args)
{
Console.WriteLine("Starting on {0}", Thread.CurrentThread.ManagedThreadId);
for (var i = 0; i < 10; i++)
{
var num = i;
Task<int>.Factory.StartNew(() =>
{
if (num == 3)
{
Thread.Sleep(20000);
}
Thread.Sleep(new Random(num).Next(1000, 10000));
Console.WriteLine("Done {0} on {1}", num, Thread.CurrentThread.ManagedThreadId);
return num;
}).ContinueWith(value =>
{
Console.WriteLine("Completed {0} on {1}", value.Result, Thread.CurrentThread.ManagedThreadId);
}
/* need syncronization context */);
}
Console.WriteLine("End of Main");
Console.ReadKey();
}
-- 解决方案 --在得到一些评论并阅读了一些解决方案之后,这就是我想要的完整解决方案。 这里的目标是尽可能快地处理多个长时间运行的任务,然后一次处理每个任务的结果。
static void Main(string[] args)
{
Console.WriteLine("Starting on {0}", Thread.CurrentThread.ManagedThreadId);
var results = new BlockingCollection<int>();
Task.Factory.StartNew(() =>
{
while (!results.IsCompleted)
{
try
{
var x = results.Take();
Console.WriteLine("Completed {0} on {1}", x, Thread.CurrentThread.ManagedThreadId);
}
catch (InvalidOperationException)
{
}
}
Console.WriteLine("\r\nNo more items to take.");
});
var tasks = new List<Task>();
for (var i = 0; i < 10; i++)
{
var num = i;
var t = Task.Factory.StartNew(() =>
{
if (num == 3)
{
Thread.Sleep(20000);
}
Thread.Sleep(new Random(num).Next(1000, 10000));
Console.WriteLine("Done {0} on {1}", num, Thread.CurrentThread.ManagedThreadId);
results.Add(num);
});
tasks.Add(t);
}
Task.Factory.ContinueWhenAll(tasks.ToArray(), _ => results.CompleteAdding());
Console.WriteLine("End of Main");
Console.ReadKey();
}
您必须创建某种类型的编写器任务,但是请记住,即使此任务也可以重新安排到另一个本机或托管线程上。 使用 TPL 中的默认调度程序,您无法控制哪个托管线程接收工作。
public class ConcurrentConsole
{
private static BlockingCollection<string> output
= new BlockingCollection<string>();
public static Task CreateWriterTask(CancellationToken token)
{
return new Task(
() =>
{
while (!token.IsCancellationRequested)
{
string nextLine = output.Take(token);
Console.WriteLine(nextLine);
}
},
token);
}
public static void WriteLine(Func<string> writeLine)
{
output.Add(writeLine());
}
}
当我将您的代码切换为使用此代码时,我收到了以下 output:
End of Main
Done 1 on 6
Completed 1 on 6
Done 5 on 9
Completed 5 on 9
Done 0 on 4
Completed 0 on 4
Done 2 on 5
Completed 2 on 13
Done 7 on 10
Completed 7 on 10
Done 4 on 8
Completed 4 on 5
Done 9 on 12
Completed 9 on 9
Done 6 on 6
Completed 6 on 5
Done 8 on 11
Completed 8 on 4
Done 3 on 7
Completed 3 on 7
即使您的代码将() => String.Format("Completed {0} on {1}"...
发送到ConcurrentConsole.WriteLine
,确保在ConcurrentConsole
任务中获取ManagedThreadId
,它仍然会改变它的哪个线程继续运行。尽管比执行任务的可变性要小。
您可以使用OrderedTaskScheduler
确保一次只运行一个任务完成; 但是,它们将在线程池线程上运行(不一定都在同一个线程上)。
如果你真的需要它们都在同一个线程上(而不是一次一个),那么你可以使用ActionThread
库中的 ActionThread 。 它为其代码提供了一个SynchronizationContext
,可以由FromCurrentSynchronizationContext
。
我会建议:
1)创建锁object
2)创建要写入的字符串列表
3)产生一个循环的线程,休眠一段时间,然后锁定字符串列表,如果它不为空,则写入所有字符串并清空列表
4)其他线程然后锁定列表,添加他们的状态,解锁并继续。
object writeListLocker = new object();
List<string> linesToWrite = new List<string>();
// Main thread loop
for (; ; )
{
lock (writerListLocker)
{
foreach (string nextLine in linesToWrite)
Console.WriteLine(nextLine);
linesToWrite.Clear();
}
Thread.Sleep(500);
}
// Reporting threads
lock (writerListLocker)
{
linesToWrite.Add("Completed (etc.)");
}
我认为您期望得到如下结果。
Starting on 8
Done 1 on 11
Completed 1 on 9
Done 5 on 11
Completed 5 on 9
Done 0 on 10
Completed 0 on 9
Done 2 on 12
Completed 2 on 9
Done 7 on 16
Completed 7 on 9
Done 4 on 14
Completed 4 on 9
Done 9 on 18
Completed 9 on 9
Done 6 on 15
Completed 6 on 9
Done 8 on 17
Completed 8 on 9
Done 3 on 13
Completed 3 on 9
如下所示,我在理解 SynchronizationContext的代码中使用了StaSynchronizationContext
,其中很好地解释了一个线程中的同步调用。 请参考一下。
我的代码片段是:
static void Main(string[] args)
{
StaSynchronizationContext context = new StaSynchronizationContext();
StaSynchronizationContext.SetSynchronizationContext(context);
Console.WriteLine("Starting on {0}", Thread.CurrentThread.ManagedThreadId);
for (var i = 0; i < 10; i++)
{
var num = i;
Task<int>.Factory.StartNew(() =>
{
if (num == 3)
{
Thread.Sleep(20000);
}
Thread.Sleep(new Random(num).Next(1000, 10000));
Console.WriteLine("Done {0} on {1}", num, Thread.CurrentThread.ManagedThreadId);
return num;
}).ContinueWith(
value =>
{
Console.WriteLine("Completed {0} on {1}", value.Result, Thread.CurrentThread.ManagedThreadId);
}
,TaskScheduler.FromCurrentSynchronizationContext());
}
Console.WriteLine("End of Main");
Console.ReadKey();
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.