5

我有一个在应用程序生命周期内运行的小型后台线程 - 但是当应用程序关闭时,线程应该正常退出。

问题是线程以 15 分钟的间隔运行一些代码 - 这意味着它睡眠很多。

现在为了让它脱离睡眠状态,我向它抛出一个中断——但是我的问题是,如果有更好的方法来解决这个问题,因为中断会产生 ThreadInterruptedException。

这是我的代码的要点(有点伪):

public class BackgroundUpdater : IDisposable
{
    private Thread myThread;
    private const int intervalTime = 900000; // 15 minutes
    public void Dispose()
    {
        myThread.Interrupt();
    }

    public void Start()
    {
        myThread = new Thread(ThreadedWork);
        myThread.IsBackground = true; // To ensure against app waiting for thread to exit
        myThread.Priority = ThreadPriority.BelowNormal;
        myThread.Start();
    }

    private void ThreadedWork()
    {
        try
        {
            while (true)
            {
                Thread.Sleep(900000); // 15 minutes
                DoWork();
            }
        }
        catch (ThreadInterruptedException)
        {
        }
    }
}
4

5 回答 5

14

绝对有更好的方法 - 要么使用Monitor.Wait/ Pulse而不是睡眠/中断,要么使用Auto/ ManualResetEventManualResetEvent(在这种情况下,您可能需要一个。)

我个人是 Wait/Pulse 的粉丝,可能是因为它类似于 Java 的 wait()/notify() 机制。但是,在某些时候重置事件肯定更有用。

您的代码将如下所示:

private readonly object padlock = new object();
private volatile bool stopping = false;

public void Stop() // Could make this Dispose if you want
{
    stopping = true;
    lock (padlock)
    {
        Monitor.Pulse(padlock);
    }
}

private void ThreadedWork()
{
    while (!stopping)
    {
        DoWork();
        lock (padlock)
        {
            Monitor.Wait(padlock, TimeSpan.FromMinutes(15));
        }
    }
}

有关更多详细信息,请参阅我的线程教程,特别是关于死锁、等待和脉冲的页面,以及关于等待句柄的页面。Joe Albahari还有一个教程,涵盖了相同的主题并进行了比较。

我还没有详细查看,但如果 Parallel Extensions 也有一些功能可以让这更容易,我不会感到惊讶。

于 2009-07-17T06:14:05.947 回答
2

您可以使用事件来检查进程是否应该像这样结束:

var eventX = new AutoResetEvent(false);
while (true)
{
    if(eventX.WaitOne(900000, false))
    {
        break;
    }
    DoWork();
}
于 2009-07-17T06:27:58.727 回答
1

.NET 4 及更高版本中有CancellationTokenSource一个类可以稍微简化此任务。

private readonly CancellationTokenSource cancellationTokenSource = 
    new CancellationTokenSource();

private void Run()
{
    while (!cancellationTokenSource.IsCancellationRequested)
    {
        DoWork();
        cancellationTokenSource.Token.WaitHandle.WaitOne(
            TimeSpan.FromMinutes(15));
    }
}

public void Stop()
{
    cancellationTokenSource.Cancel();
}

不要忘记它CancellationTokenSource是一次性的,因此请确保正确处理它。

于 2013-10-14T03:49:51.153 回答
0

一种方法可能是添加线程将订阅的取消事件或委托。当取消事件被调用时,线程可以自行停止。

于 2009-07-17T06:14:13.390 回答
0

我非常喜欢 Jon Skeets 的回答。但是,这可能更容易理解并且也应该有效:

public class BackgroundTask : IDisposable
{
    private readonly CancellationTokenSource cancellationTokenSource;
    private bool stop;

    public BackgroundTask()
    {
        this.cancellationTokenSource = new CancellationTokenSource();
        this.stop = false;
    }

    public void Stop()
    {
        this.stop = true;
        this.cancellationTokenSource.Cancel();
    }

    public void Dispose()
    {
        this.cancellationTokenSource.Dispose();
    }

    private void ThreadedWork(object state)
    {
        using (var syncHandle = new ManualResetEventSlim())
        {
            while (!this.stop)
            {
                syncHandle.Wait(TimeSpan.FromMinutes(15), this.cancellationTokenSource.Token);
                if (!this.cancellationTokenSource.IsCancellationRequested)
                {
                    // DoWork();
                }
            }
        }
    }
}

或者,包括等待后台任务实际停止(在这种情况下,Dispose 必须由后台线程正在运行的线程之外的其他线程调用,当然这不是完美的代码,它需要工作线程实际已经开始):

using System;
using System.Threading;

public class BackgroundTask : IDisposable
{
    private readonly ManualResetEventSlim threadedWorkEndSyncHandle;
    private readonly CancellationTokenSource cancellationTokenSource;
    private bool stop;

    public BackgroundTask()
    {
        this.threadedWorkEndSyncHandle = new ManualResetEventSlim();
        this.cancellationTokenSource = new CancellationTokenSource();
        this.stop = false;
    }

    public void Dispose()
    {
        this.stop = true;
        this.cancellationTokenSource.Cancel();
        this.threadedWorkEndSyncHandle.Wait();
        this.cancellationTokenSource.Dispose();
        this.threadedWorkEndSyncHandle.Dispose();
    }

    private void ThreadedWork(object state)
    {
        try
        {
            using (var syncHandle = new ManualResetEventSlim())
            {
                while (!this.stop)
                {
                    syncHandle.Wait(TimeSpan.FromMinutes(15), this.cancellationTokenSource.Token);
                    if (!this.cancellationTokenSource.IsCancellationRequested)
                    {
                        // DoWork();
                    }
                }
            }
        }
        finally
        {
            this.threadedWorkEndSyncHandle.Set();
        }
    }
}

如果您发现 Jon Skeets 解决方案有任何缺陷和缺点,我想听听它们,因为我总是喜欢学习 ;-) 我想这会更慢并且使用更多内存,因此不应该在大规模和短时间内使用。任何其他?

于 2013-03-07T10:16:30.450 回答