问题
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