Task.Factory.StartNew保证执行顺序

3

我有两个线程调用“Task.Factory.StartNew”。假设一个线程(ThreadA)比另一个线程(ThreadB)稍微领先。

...在稍微领先的线程A中被调用

Task.Run(() => {
  SomeMethodInThreadA();
});

...在稍后调用的B线程中

Task.Run(() => {
  SomeMethodInThreadB();
});

TaskScheduler是否保证在执行SomeMethodInThreadB之前先执行SomeMethodInThreadA

如果不是,我该怎么做?使用Task.Factory.StartNew并传递特殊的TaskScheduler吗?

此外,除了确保先处理SomeMethodInThreadA之外,我还想确保SomeMethodInThreadA在执行SomeMethodInThreadB之前先完成。

我已经研究过使用StaTaskScheduler,但我不确定这是否是解决方案。

编辑:

没有透露关于我继承的程序的太多细节/戏剧性,我想要的是一个自定义的TaskScheduler,它:

  1. 尊重委托排队的顺序

  2. 串行执行它们

  3. 在同一线程中执行,类似于我上面链接的StaTaskScheduler源代码


除非您实际上使用不同的调度程序(我可能不建议),否则不要使用StartNew http://blog.stephencleary.com/2013/08/startnew-is-dangerous.html。 - Chris Marisic
嗯,我有点必须这样做,因为我需要一个类似于 StaTaskScheduler 的特殊 TaskScheduler。 - alpinescrambler
我觉得你正在解决错误的问题。 - Chris Marisic
如果有这样的保证,那么所有任务都将在全局范围内串行化。这与只有一个线程运行任务是相同的。 - usr
假如我们这样做 { SomeMethodInThreadA(); SomeMethodInThreadB(); } 怎么样? - usr
4个回答

5
我继承了一个程序,不想透露太多细节和纷争。我正在寻找一个自定义任务调度程序,它应该:
  1. 按照排队的顺序执行委托

  2. 串行执行

  3. 在同一线程中执行,类似于我以上链接的StaTaskScheduler源代码

如果你只需要这些,那么你需要创建一个 BlockingCollection<Action> ,然后在专门的线程上遍历列表。
public class BackgroundJobSchedueller
{
    readonly BlockingCollection<Action> _queue;
    readonly Thread _thread;

    public BackgroundJobSchedueller()
    {
        _queue = new BlockingCollection<Action>()
        _thread = new Thread(WorkThread)
        {
            IsBackground = true,
            Name = "Background Queue Processor"
        };
        _thread.Start();
    }

    public void StopSchedueller()
    {
        //Tell GetConsumingEnumerable() to let the user out of the foreach loop
        // once the collection is empty.
        _queue.CompleteAdding();

        //Wait for the foreach loop to finish processing.
        _thread.Join();
    }

    public void QueueJob(Action job)
    {
        _queue.Add(job);
    }

    void WorkThread()
    {
        foreach(var action in _queue.GetConsumingEnumerable())
        {
            try
            {
                action();
            }
            catch
            {
                //Do something with the exception here
            }
        }
    }
}

当没有任务可执行时,线程会睡眠,在foreach上被阻塞,一旦向队列中添加了一个项目,它将被处理然后再次进入睡眠状态。

哦,亲爱的,这个可行!尤其是我的委托有相同的签名! - alpinescrambler
еҚідҢүж‚Ёзљ„ж–№жі•жІҰжњ‰з›ёеђЊзљ„з­ңеђҚпәЊж‚Ёд»Қ然еЏҮд»ӨйЂљиү‡е°†иҮӨж–№жі•еЊ…иӘ…ењЁlambdaдё­жқӨдҢүз”Ёе®ѓschedueller.QueueJob(() => SomeMethodWithParams(42))гЂ‚ - Scott Chamberlain

0
任务调度器能保证在执行SomeMethodInThreadB之前,SomeMethodInThreadA会被先执行吗?
不行。
如果不能,我该怎么做呢?
你可以使用ContinueWith将第二个任务作为第一个任务的延续。
例如:
var task = Task.Run(() => {
    SomeMethodInThreadA();
});

然后稍后:

task.ContinueWith(t => {
    SomeOtherMethodInThreadA();
});

这2个线程不一定相互通信,所以我无法获取那个“任务”变量。 - alpinescrambler
@alpinescrambler:当然可以。而且它存在于父线程中,而不是线程A中。 - Matt Burland
我的意思是,这两个线程是独立编写的。没有传递你在示例中捕获的“任务”变量的功能。 - alpinescrambler
3
那么你需要修复这个问题。一个任务不能依赖于它不允许了解任何东西的内容。你需要设计你的程序,使具有依赖关系的操作接受继续完成的“任务”(Task),或者类似这样的东西。 - Servy
@alpinescrambler:你不需要在不同的线程之间传递任何东西。启动第一个任务的父线程是task存在的地方。然后,在同一父线程中添加继续执行。线程A不需要知道任务的任何信息(实际上,也没有线程B)。如果两个任务是在不同的方法中创建的,那么task可能会超出范围,那么只需将其放入成员字段即可。 - Matt Burland

0
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            Task.Factory.StartNew(() => MethodA())
                .ContinueWith(a =>
                {
                     MethodB();
                });

            Console.WriteLine("Press a key to exit");
            Console.ReadKey(false);

        }

        public static void MethodA()
        {
            Thread.Sleep(2000);
            Console.WriteLine("Hello From method A {0}", Thread.CurrentThread.ManagedThreadId);
        }

        public static void MethodB()
        {
            Thread.Sleep(1000);
            Console.WriteLine("Hello From method B {0}", Thread.CurrentThread.ManagedThreadId);
        }
    }
}

0
任务调度器是否保证在执行SomeMethodInThreadB之前先执行SomeMethodInThreadA?
当然不是。
如果不是,我该怎么办?
如果这2个线程彼此完全独立,但是SomeMethodInThreadB必须在SomeMethodInThreadA完成后执行,您可以使用标准的线程同步例程:
public static AutoResetEvent Wait = new AutoResetEvent(false);

然后当开始第一个任务时:

var task = Task.Run(() => 
{
    // Sets the state of the event to nonsignaled, causing threads to block.
    Wait.Reset();

    // Execute the first task
    SomeMethodInThreadA();

    // The first task has finished executing -> signal the second
    // thread which is probably waiting that it can start processing
    // the second task
    Wait.Set();
});

在第二个线程中等待被通知第一个方法已经执行完毕:

Task.Run(() => 
{
    if (!Wait.WaitOne(TimeSpan.FromMinutes(30)))
    {
        // We have waited for too long the first task to execute - giving up
    }
    else
    {
        // The first task has finished executing - we can now
        // proceed with the second task
        SomeMethodInThreadB();
    }
});

显然,由您定义第二个线程在放弃之前等待第一个任务完成的时间。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接