没有UI线程的任务同步

本文关键字:任务 同步 线程 UI 没有 | 更新日期: 2023-09-27 18:00:48

在下面的代码中,我想同步任务列表的结果报告。这现在正在工作,因为任务。在任务完成之前,结果将被阻止。但是,任务id=3需要很长时间才能完成,并阻止所有其他已完成的任务报告其状态。

我想我可以通过将报告(Console.Write(移动到.ContinueWith指令中来完成这项工作,但我没有UI线程,所以我如何让TaskScheduler同步.ContinuteWith任务?

我现在拥有的:

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

我想移动到这个或类似的东西,但我需要控制台。写("完成…"(所有发生在同一个线程上:

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

没有UI线程的任务同步

您必须创建某种编写器任务,但是,请记住,即使任务也可以重新安排到另一个本机或托管线程上!使用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());
    }
}

当我把你的代码切换到使用这个时,我收到了以下输出:

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,确保ManagedThreadIdConcurrentConsole任务上被拾取,它仍然会改变它在哪个线程上运行。尽管与执行任务相比变化较小。

您可以使用OrderedTaskScheduler来确保一次只运行一个任务完成;但是,它们将在线程池线程上运行(不一定都在同一个线程上(。

如果您真的需要在同一个线程上全部使用它们(而不是一次只使用一个(,那么您可以使用Nito.Async库中的ActionThread。它为其代码提供了一个SynchronizationContextFromCurrentSynchronizationContext可以拾取该代码。

我建议:

1( 创建锁定对象
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();
}