在C#内部使用TPL内部服务的基本设计模式

ado*_*lot 8 c# multithreading windows-services task-parallel-library

我正在尝试构建Windows服务,它需要某种并行性来汇集来自不同ftp源的文件.为了启动多个ftp下载,我正在寻找TPL库来轻松地进行foreach循环并使并行性变得非常容易.但是,当我搜索如何启动或停止我的服务时,我最好的资金是在OnStart()方法中创建新线程,如下所述/sf/answers/340612541/

阅读TPL总是注意到TPL比手动线程和手动停止线程更先进.

我没有找到任何描述如何在WindowsService中进行TPL循环的示例帖子?

我的代码:

protected override void OnStart(string[] args)
{
     _thread = new Thread(WorkerThreadFunc);
     _thread.Name = "My Worker Thread";
     _thread.IsBackground = true;
     _thread.Start();
}
Run Code Online (Sandbox Code Playgroud)

并在里面WorkerThreadFunc做了一些TPL

private void WorkerThreadFunc()
{
foreach (string path in paths)
{
    string pathCopy = path;
    var task = Task.Factory.StartNew(() =>
        {
            Boolean taskResult = ProcessPicture(pathCopy);
            return taskResult;
        });
    task.ContinueWith(t => result &= t.Result);
    tasks.Add(task);
}

}
Run Code Online (Sandbox Code Playgroud)

或者我应该开始我的WorkerThreadFunc作为TASK?

t3z*_*t3z 23

这里需要注意的不仅仅是你如何开始你的工人,还有你如何实际阻止它.

除了Task本身,TPL还通过使用和对象为您提供了一种非常方便的任务取消方式.CancellationTokenCancellationTokenSource

使用TPL启动和停止Windows服务

要将此技术应用于Windows服务,您基本上需要执行以下操作:

    private CancellationTokenSource tokenSource;
    private Task backgroundTask;

    protected override void OnStart(string[] args)
    {
        tokenSource = new CancellationTokenSource();
        var cancellation = tokenSource.Token;
        backgroundTask = Task.Factory.StartNew(() => WorkerThreadFunc(cancellation),
                                    cancellation,
                                    TaskCreationOptions.LongRunning,
                                    TaskScheduler.Default);
    }
Run Code Online (Sandbox Code Playgroud)

笔记:

  • TaskCreationOptions.LongRunning 提示任务调度程序,此任务可能需要一个单独的线程,并避免将其放在ThreadPool上(以便它不会阻止池中的其他项)
  • CancellationToken 传递给WorkerThreadFunc,因为这是通知它停止工作的方式 - 请参阅下面的取消部分

在OnStop方法中,您触发取消并等待任务完成:

    protected override void OnStop()
    {
        bool finishedSuccessfully = false;
        try
        {
            tokenSource.Cancel();
            var timeout = TimeSpan.FromSeconds(3);
            finishedSuccessfully = backgroundTask.Wait(timeout);
        }
        finally
        {
            if (finishedSuccessfully == false)
            {
                // Task didn't complete during reasonable amount of time
                // Fix your cancellation handling in WorkerThreadFunc or ProcessPicture
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

消除

通过调用tokenSource.Cancel();我们只是告诉每个CancellationToken由此发出的tokenSource取消,并且接受此类令牌的每个方法(如您的WorkerThreadFunc)现在应该停止其工作.

处理取消特定于实现,但一般规则是您的方法应该监视取消令牌状态并能够在合理的时间内停止其工作.这种方法要求您在逻辑上将您的工作分成更小的部分,这样您就不会停留在需要大量时间完成的工作上,如果请求取消则不会开始任何新工作.

查看您的WorkerThreadFunc代码,您可以考虑执行每个新ProcessPicture任务之前检查取消,例如:

private List<Task> tasks = new List<Task>();

private void WorkerThreadFunc(CancellationToken token)
{
    foreach (string path in paths)
    {
        if (token.IsCancellationRequested)
        {
            // you may also want to pass a timeout value here to handle 'stuck' processing
            Task.WaitAll(tasks.ToArray());

            // no more new tasks
            break;
        }

        string pathCopy = path;
        var task = Task.Factory.StartNew(() =>
            {
                Boolean taskResult = ProcessPicture(pathCopy, token); // <-- consider a cancellation here
                return taskResult;
            }, token); // <<--- using overload with cancellation token
        task.ContinueWith(t => result &= t.Result);
        tasks.Add(task);
    }
}
Run Code Online (Sandbox Code Playgroud)

如果ProcessPicture需要很长时间才能完成,您可能还需要在其中添加取消支持.与WorkerThreadFunc类似,您应该考虑ProcessPicture实现.这里的关键想法是找到一个可以安全地停止工作并从方法返回的地方.通过安全我的意思是- 没有留下系统或数据的损坏状态.

除了IsCancellationRequested在WorkerThreadFunc中进行监视之外,还可以Register在请求取消时执行回调,以执行其他一些操作,例如清理等:

token.Register(CancellationCallback);
Run Code Online (Sandbox Code Playgroud)

private void CancellationCallback()
{
    // wait for all tasks to finish

    // cleanup
}
Run Code Online (Sandbox Code Playgroud)