优雅地杀死TPL任务

时间:2011-11-11 07:32:25

标签: c#-4.0 asynchronous task-parallel-library

我正在尝试针对一组REST服务构建类似API的东西。基本上有不同的操作报告其进度和支持取消。我选择使用TPL来创建异步功能和管理负载。这是我最基本的操作,可以相应地继承:

using FileOnlineCore.Enums;
using FileOnlineCore.EventArguments;
using FileOnlineCore.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace FileOnlineCore.Operations {

    public abstract class BaseOperation {

        #region Store
        private readonly Task _InnerTask;
        private readonly CancellationTokenSource _InnerCancelTokenSource;
        private readonly CancellationToken _InnerCancelToken;

        public Task InnerTask {
            get { return _InnerTask; }
        }

        protected CancellationTokenSource InnerCancelTokenSource {
            get { return _InnerCancelTokenSource; }
        }

        protected CancellationToken InnerCancelToken {
            get { return _InnerCancelToken; }
        }

        public Int32 TaskId {
            get { return _InnerTask != null ? _InnerTask.Id : -1; }
        }

        public OperationRunningStatus Status { get; set; }
        public OperationType TaskType { get; set; }
        public OperationProgressReportEventHandler onProgressReport;
        public OperationCompletedEventHandler onOperationCompleted;
        #endregion

        public BaseOperation() {
            _InnerCancelTokenSource = new CancellationTokenSource();
            _InnerCancelToken = _InnerCancelTokenSource.Token;
            _InnerTask = new Task(() => PerformWork(), _InnerCancelToken, TaskCreationOptions.None);
            Status = OperationRunningStatus.Idle;
        }

        public void Start() {
            _InnerTask.Start();
            Status = OperationRunningStatus.Working;
        }

        public void Cancel() {
            _InnerCancelTokenSource.Cancel();
            Status = OperationRunningStatus.Completed;
        }

        protected abstract void PerformWork();

        protected void ReportProgress(int Progress) {
            if (onProgressReport == null)
                return;

            onProgressReport(new OperationProgressEventArg { 
                Progress = Progress, 
                TaskId = TaskId
            });
        }

        protected void TaskCompleted(RemoteOperationEventArg arg) {
            if (onOperationCompleted == null)
                return;

            onOperationCompleted(arg);
            Status = OperationRunningStatus.Completed;
        }

    }

}

如您所见,我正在使用delegates来完成要求。就像Thread类一样,Task类不能被继承,因此Task对象实际上是在我的BaseOperation中组成的。这是实施的实际操作:

using FileOnlineCore.EventArguments;
using FileOnlineCore.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using FileOnlineCore.Objects;
using FileOnlineCore.Enums;

namespace FileOnlineCore.Operations {

    public class DownloadObjectOperation : BaseOperation {

        private readonly FileSystemObject _FileSystemObject;
        private readonly String _LocalFolder;

        public FileSystemObjectDownloadCompletedEventHandler onDownloadCompleted;

        public DownloadObjectOperation(FileSystemObject FileSystemObject, String LocalFolder){
            TaskType = OperationType.DownloadFile;

            _FileSystemObject = FileSystemObject;
            _LocalFolder = LocalFolder;
        }

        protected override void PerformWork() {
            try {
                ReportProgress(0);

                for (int i = 1; i <= 100; i+=5) {
                    // Check for the cancellation to be signaled
                    InnerCancelToken.ThrowIfCancellationRequested();

                    // Write out a little bit showing the progress of the task
                    //Console.WriteLine("Task {0}: {1}/100 In progress", Task.CurrentId, i + 1);
                    Thread.Sleep(50); // Simulate doing some work

                    // Report progress of the work.
                    ReportProgress(i);
                }

                // By getting here the task will RunToCompletion even if the token has been signaled.

                base.TaskCompleted(new RemoteOperationEventArg { 
                    Error = null, 
                    ResultSource = OperationResultSource.Unknown, 
                    Status = OperationExitStatus.Success, 
                    TaskId = TaskId 
                });

                this.DownloadTaskCompleted(new DownloadObjectOperationEventArg { 
                    Error = null, 
                    ResultSource = OperationResultSource.Unknown, 
                    Status = OperationExitStatus.Success, 
                    TaskId = TaskId, 
                    ObtainedFileSystemObject = null
                }, _LocalFolder, "TheFileName.Extension");
            }
            catch (OperationCanceledException exp_Canceled) {
                // Any clean up code goes here.

                base.TaskCompleted(new RemoteOperationEventArg {
                    Error = exp_Canceled,
                    ResultSource = OperationResultSource.Unknown,
                    Status = OperationExitStatus.Error,
                    TaskId = TaskId
                });

                this.DownloadTaskCompleted(new DownloadObjectOperationEventArg {
                    Error = exp_Canceled,
                    ResultSource = OperationResultSource.Unknown,
                    Status = OperationExitStatus.Error,
                    TaskId = TaskId,
                    ObtainedFileSystemObject = null
                }, _LocalFolder, "TheFileName.Extension");

                // To ensure that the calling code knows the task was cancelled
                //throw; 
            }
            catch (Exception exp) {
                // Clean up other stuff

                base.TaskCompleted(new RemoteOperationEventArg { 
                    Error = exp, 
                    ResultSource = OperationResultSource.Unknown, 
                    Status = OperationExitStatus.Error, 
                    TaskId = TaskId 
                });

                this.DownloadTaskCompleted( new DownloadObjectOperationEventArg { 
                    Error = exp, 
                    ResultSource = OperationResultSource.Unknown, 
                    Status = OperationExitStatus.Error, 
                    TaskId = TaskId 
                }, _LocalFolder, "TheFileName.Extension");

                // If the calling code also needs to know.
                //throw;
            }
        }

        protected void DownloadTaskCompleted(DownloadObjectOperationEventArg arg, String LocalFolder, String FileName) {
            if (onDownloadCompleted == null)
                return;

            onDownloadCompleted(arg, LocalFolder, FileName);
            Status = OperationRunningStatus.Completed;


        }

    }

}

现在,这是一个公开单个方法的主类,该方法依次完成所有管道创建DownloadObjectOperation对象并使其工作:

using FileOnlineCore.Enums;
using FileOnlineCore.Events;
using FileOnlineCore.Objects;
using FileOnlineCore.Operations;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace FileOnlineCore {

    public sealed class FileOnline {

        // Construct a task scheduler from the current SynchronizationContext (UI thread)
        private TaskScheduler OperationTaskScheduler;

        // Construct a new TaskFactory using our UI scheduler
        private TaskFactory OperationFactory;

        //Something to manage my operations
        private List<BaseOperation> OperationPool;

        //Originally here to massively cancel all operations. Epic fail so far.
        private CancellationTokenSource CancelAllTokenSource;
        private CancellationToken CancelAllToken;

        public FileOnline() {
            InitializeStore();
        }

        private void InitializeStore() {
            OperationPool = new List<BaseOperation>();      

            CancelAllTokenSource = new CancellationTokenSource();
            CancelAllToken = CancelAllTokenSource.Token;

            OperationTaskScheduler = TaskScheduler.Default;
            OperationFactory = new TaskFactory(CancelAllToken, TaskCreationOptions.PreferFairness, TaskContinuationOptions.ExecuteSynchronously, OperationTaskScheduler);

            //Low leve thrading stuff for performence optimization
            //int workerThreads, complete;
            //ThreadPool.GetMinThreads(out workerThreads, out complete);
            //Console.WriteLine("Idle worker threads: " + workerThreads);

            // Comment out this line to see the difference. With this commented out, the second iteration will be immediate
            //ThreadPool.SetMinThreads(100, complete);
        }


        public Int32 DownloadFileSystemObject(FileSystemObject FileSystemObject, String LocalFolder, Boolean Overwrite, OperationProgressReportEventHandler onOperationProgressReport, FileSystemObjectDownloadCompletedEventHandler onOperationCompleted) {

            var DownloadOp = new DownloadObjectOperation(FileSystemObject, LocalFolder) {
                onProgressReport = onOperationProgressReport,
                onDownloadCompleted = onOperationCompleted
            };

            OperationPool.Add(DownloadOp);

            OperationFactory.StartNew(() => {
                DownloadOp.Start();
            }).ContinueWith(t => {
                DownloadOp.InnerTask.Dispose(); //Exception!
                t.Dispose();
                OperationPool.Remove(DownloadOp);
                DownloadOp = null;
            });

            return DownloadOp.TaskId;
        }

        ...

        public void CancelTask(Int32 TaskId) {
            var FoundOperation = OperationPool.SingleOrDefault(where => where.TaskId == TaskId);

            if (FoundOperation != null && FoundOperation.InnerTask.Status == TaskStatus.Running && FoundOperation.Status == OperationRunningStatus.Working) {
                FoundOperation.Cancel();
            }
        }
        public void CancelAllTasks() {
            OperationPool.ForEach(Item => {
                if (Item != null && Item.InnerTask.Status == TaskStatus.Running && Item.Status == OperationRunningStatus.Working) {
                    Item.Cancel();
                }
            });
        }

    }

}

现在我可以直接跳过OperationFactory(我的自定义TaskFactory)并直接启动DownloadObjectOperation.Start()但是从我在网上看到的内容,TaskFatory会在内部进行负载均衡在ThreadPool

如果我跳过OperationFactory,我可以像这样轻松链接ContnueWith:

DownloadOp.InnerTask.ContinueWith(t => {
                t.Dispose();
                OperationPool.Remove(DownloadOp);
                DownloadOp = null;
            });

但是,使用OperationFactory,我得到一个异常,因为序列不等待进程完成并立即跳转到ContinueWith块。现在DownloadOp.InnerTask.Dispose();导致异常,因为实际的内部任务仍在运行。

我需要优雅地结束我的任务,因为这个API将被很多人使用。如果未正确处理Tasks或我的BaseOperations,我担心服务器会挂起。

0 个答案:

没有答案