【问题标题】:Running a long-running Task within a Windows Service在 Windows 服务中运行长时间运行的任务
【发布时间】:2014-09-19 08:55:15
【问题描述】:

我编写了一个 Windows 服务项目,该项目托管一个长时间运行的消息泵任务,该任务旨在在服务期间运行。当服务启动时,它会启动任务。当服务停止时,它会停止任务:

void OnStart()
{
    MessagePump.Start();
}

void OnStop()
{
    MessagePump.Stop();
}

MessagePump.Start 执行 Task.Factory.StartNew,MessagePump.Stop 指示任务停止并执行 Wait()。

到目前为止一切都很好,但我想知道如何最好地处理错误。如果任务有未处理的异常,我希望服务停止,但由于通常没有等待任务,我想它只会坐在那里什么都不做。我该如何优雅地处理这种情况?

更新:

共识似乎是使用'await'或ContinueWith。以下是我目前编写 Start 方法以使用它的方式:

public async static void Start()
{
    this.state = MessagePumpState.Running;
    this.task = Task.Factory.StartNew(() => this.ProcessLoop(), TaskCreationOptions.LongRunning);
    try
    {
        await this.task;
    }
    catch
    {
        this.state = MessagePumpState.Faulted;
        throw;
    }
}

【问题讨论】:

  • 我建议您使用带有取消选项的 async/await。当您有未处理的异常时,请取消,您的程序将知道错误
  • 我可以在 Windows 服务中执行此操作吗?我的理解是,如果我在 OnStart() 中进行等待,那么在任务完成之前它不会退出? (可能是错的)
  • 嗯。不等待 MessagePump.Start() 上的 Task 怎么样?检查这个博客,也许你会找到你要找的东西。 blog.stephencleary.com

标签: c# windows-services task-parallel-library


【解决方案1】:

让你 MessagePump.Start() 方法返回任务。那么

MessagePump.Start().ContinueWith(t => 
{
    // handle exception 
}, 
TaskContinuationOptions.OnlyOnFaulted);

更新: 我会做下一个:

private MessagePump _messagePump;

async void OnStart()
{
    this._messagePump = new MessagePump();
    try
    {
        // make Start method return the task to be able to handle bubbling exception here
        await _messagePump.Start();
    }
    catch (Exception ex)
    {
        // log exception
        // abort service
    }
}

void OnStop()
{
    _messagePump.Stop();
}

public enum MessagePumpState
{
    Running,
    Faulted
}

public class MessagePump
{
    private CancellationTokenSource _cancallationTokenSrc;
    private MessagePumpState _state;
    public async Task Start()
    {
        if (_cancallationTokenSrc != null)
        {
            throw new InvalidOperationException("Task is already running!");
        }

        this._state = MessagePumpState.Running;
        _cancallationTokenSrc = new CancellationTokenSource();
        var task = Task.Factory.StartNew(() => this.ProcessLoop(_cancallationTokenSrc.Token), _cancallationTokenSrc.Token);
        try
        {
            await task;
        }
        catch
        {
            this._state = MessagePumpState.Faulted;
            throw;
        }
    }

    public void Stop()
    {
        if (_cancallationTokenSrc != null)
        {
            _cancallationTokenSrc.Cancel();
            _cancallationTokenSrc = null;
        }
    }

    public void ProcessLoop(CancellationToken token)
    {
        // check if task has been canceled
        while (!token.IsCancellationRequested)
        {
            Console.WriteLine(DateTime.Now);
            Thread.Sleep(1000);
        }
    }
}

【讨论】:

  • 大概如果我使用 await,代码执行大概会坐在那里等待任务结束而不退出 OnStart?
  • @Barguast 将此逻辑包装在异步方法中,在那里使用 await,并丢弃此异步方法返回的任务。这样你就不必处理讨厌的延续,程序会继续。
  • @Barguast 不,await 不会阻塞您的调用线程。它与 ContinueWith (几乎)相同。
  • @sedovav - 谢谢,这很有帮助。我已经用我的 Start 方法现在的样子更新了我的第一篇文章。它使用 await 和异常处理程序(仅用于设置成员字段)并且似乎按我的预期工作。这是一个“异步无效”,虽然看起来有点奇怪,但返回一个任务(并避免编译器警告)更有意义。如果问得不算多,你能不能看一下我做错了什么?
  • @Barguast 将代码包装在异步任务中并丢弃生成的任务。添加代码注释。 Async void 以您可能不希望(或不理解)的方式与同步上下文交互。
【解决方案2】:

你可以试试这样的:

void OnStart()
{
    MessagePump.StartAsync(); 
    MessagePump.ErrorEvent += OnError();
}

然后您的 StartAsync 将如下所示:

public async Task StartAsync()
{
     // your process
     // if error, send event to messagePump
}

如果您决定使用 Tasks,那么最好使用 Task.Run 而不是 Task.Factory.StartNew()

【讨论】:

  • 谢谢。我在课堂上添加了一个“故障”事件,这似乎是 ServiceHost 之类的处理方式。你的最后一句话也很有趣——我认为 Task.Run 与 Task.Factory.StartNew 基本相同,但减去了一些复杂的选项。我想我至少应该在我的任务上设置 LongRunning。不确定 Task.Run 是否还有其他好处?
最近更新 更多