Correct way to handle task cancelation

橙三吉。 提交于 2021-01-28 06:34:28

问题


I am experiencing some weird behaviour with a windows service application I am working on. This is my 1st dip into Tasks so I am on a steep learning curve and in need of some assistance as I know my issue is probably down to something I have misunderstood.

I have the following setup:

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


}
}

What I am seeing is that the cancel and start are working fine for me for the 1st change detected but subsequent cancels issued after other changes are not working. I can see the call to the event method happening, however, it appears to be calling on the original instance of the page indexer.

I am sure I have just got to a point where I have been going around so long I have made a complete mess, but I would be grateful for any guidance anyone can offer to get me back on the right track

Thank you in advance.

Regards


回答1:


A CancellationTokenSource and CancellationToken can only be signaled once. They become cancelled forever. If you want multiple cancellation signals for multiple threads/tasks then you need one token for each such operation.

Often, it is a good pattern to group them in a class:

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

That way there automatically is a 1:1 association of task and token. You are able to cancel a specific task this way.



来源:https://stackoverflow.com/questions/53212349/correct-way-to-handle-task-cancelation

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!