处理任务取消的正确方法

时间:2018-11-08 16:44:29

标签: c# multithreading

我正在使用的Windows服务应用程序遇到一些奇怪的行为。这是我第一次接触Tasks,所以我的学习曲线很陡,需要一些帮助,因为我知道我的问题很可能是由于我误解了。

我有以下设置:

public partial class MyService 
{
    protected override void OnStart(string[] args)
    {
        MasterTokenSource = new CancellationTokenSource();
        MasterCancellationToken = MasterTokenSource.Token;

        //Begin tasks.                
        StartAllTasks();


        //This is the thread that is going to listen for updates in the database.
        Task MasterService = Task.Factory.StartNew(() =>
        {
            while (!MasterCancellationToken.IsCancellationRequested)
            {
                //Sleep for the amount of time as determined in the DB
                Thread.Sleep(ServiceInstance.PollInterval * 1000);
                Console.WriteLine("Polled for changes");
                //Check service modules for changes as per DB config
                UpdateServiceModulePropertiesAndRunningTasks();
                //MasterTokenSource.Cancel();
            }
            MasterCancellationToken.ThrowIfCancellationRequested();
        }, MasterCancellationToken);
    }

    private void StartAllTasks()
    {
        //Index pages task 
        ServiceModule PageIndexersm = ServiceInstance.GetServiceModule("PageIndexer");
        PageIndexer.StartNewInstance(PageIndexersm, ConfigInstance, MasterTokenSource);

        //There are other calls to other methods to do different things here but they all follow the same logic
    }

    private void UpdateServiceModulePropertiesAndRunningTasks()
    {
        //Get a fresh copy of the service instance, and compare to current values 
        ServiceInstance compareServiceInstance = new ServiceInstance(ConfigInstance.OneConnectionString, ConfigInstance.TwoConnectionString, ConfigInstance.ServiceName);

        foreach (ServiceModule NewServiceModuleItem in compareServiceInstance.AllServiceModules)
        {
            ServiceModule CurrentServiceModuleInstance = ServiceInstance.GetServiceModule(NewServiceModuleItem.ModuleName);

            if (!NewServiceModuleItem.Equals(CurrentServiceModuleInstance))
            {
                //Trigger changed event and pass new instance
                CurrentServiceModuleInstance.On_SomethingChanged(NewServiceModuleItem, MasterTokenSource);
            }
        }
    }
}

public class PageIndexer
{
public ServiceConfig ServiceConfig { get; set; }
public ServiceModule ServiceModuleInstance { get; set; }
public Guid InstanceGUID { get; set; }
public CancellationTokenSource TokenSource { get; set; }
public CancellationToken Token { get; set; }

public PageIndexer(ServiceModule PageIndexerServiceModule, ServiceConfig _ServiceConfig)
{
    ServiceModuleInstance = PageIndexerServiceModule;
    ServiceModuleInstance.SomethingChanged += ServiceModuleInstance_SomethingChanged;
    ServiceConfig = _ServiceConfig;
    InstanceGUID = Guid.NewGuid();
}

//This is the method called within the PageIndexer instance
private void ServiceModuleInstance_SomethingChanged(ServiceModule sm, CancellationTokenSource MasterCancelToken)
{
    Console.WriteLine(InstanceGUID + ": Something changed");

    TokenSource.Cancel();

    //Start new indexer instance            
    PageIndexer.StartNewInstance(sm, ServiceConfig, MasterCancelToken);
}

public void RunTask()
{
    Console.WriteLine("Starting Page Indexing");
    Task.Factory.StartNew(() =>
    {
        while (true)
        {
            if (TokenSource.Token.IsCancellationRequested)
            {
                Console.WriteLine(InstanceGUID + ": Page index CANCEL requested: " + TokenSource.IsCancellationRequested);
                TokenSource.Token.ThrowIfCancellationRequested();
            }

            if (ServiceModuleInstance.ShouldTaskBeRun())
            {
                Console.WriteLine(InstanceGUID + ": RUNNING full index, Cancellation requested: " + TokenSource.IsCancellationRequested);
                RunFullIndex();
            }
            else
            {
                Console.WriteLine(InstanceGUID + ": SLEEPING, module off, Cancellation requested: " + TokenSource.IsCancellationRequested);
                //If the task should not be run then sleep for a bit to save resources
                Thread.Sleep(5000);
            }
        }
    }, TokenSource.Token);
}

public static void StartNewInstance(ServiceModule serviceModule, ServiceConfig eServiceConfig, CancellationTokenSource MasterCancellationToken)
{
    PageIndexer pageIndexerInstance = new PageIndexer(serviceModule, eServiceConfig);
    CancellationTokenSource NewInstanceCancellationTokenSource = new CancellationTokenSource();
    NewInstanceCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(MasterCancellationToken.Token);


    pageIndexerInstance.TokenSource = NewInstanceCancellationTokenSource;
    pageIndexerInstance.Token = pageIndexerInstance.TokenSource.Token;
    pageIndexerInstance.RunTask();


}
}

我看到的是,对于检测到的第一个更改,取消和开始对我来说工作正常,但是在其他更改不起作用之后发出的后续取消不起作用。我可以看到对事件方法的调用正在发生,但是,它似乎正在对页面索引器的原始实例进行调用。

我确定我已经走了很长时间,已经彻底搞砸了,但是我很感谢任何人都可以提供的指导,让我回到正确的轨道上

谢谢。

致谢

1 个答案:

答案 0 :(得分:0)

CancellationTokenSourceCancellationToken只能发信号一次。他们永远被取消。如果您需要多个线程/任务的多个取消信号,则每个此类操作都需要一个令牌。

通常,将它们分组在一个类中是一个很好的模式:

class MyOperation {
 Task task; //use this for waiting
 CancellationTokenSource cts; //use this for cancelling
}

这样,任务和令牌将自动建立1:1关联。您可以通过这种方式取消特定任务。