关于c#:使用Thread.Sleep()总是坏的吗?

Is it always bad to use Thread.Sleep()?

我为类Random创建了一个扩展方法,它在随机时间执行Action(void委托):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public static class RandomExtension
{
    private static bool _isAlive;
    private static Task _executer;

    public static void ExecuteRandomAsync(this Random random, int min, int max, int minDuration, Action action)
    {
        Task outerTask = Task.Factory.StartNew(() =>
        {
            _isAlive = true;
            _executer = Task.Factory.StartNew(() => { ExecuteRandom(min, max, action); });
            Thread.Sleep(minDuration);
            StopExecuter();
        });
    }

    private static void StopExecuter()
    {
        _isAlive = false;
        _executer.Wait();

        _executer.Dispose();
        _executer = null;
    }

    private static void ExecuteRandom(int min, int max, Action action)
    {
        Random random = new Random();

        while (_isAlive)
        {
            Thread.Sleep(random.Next(min, max));
            action();
        }
    }
}

它很好用。

但是在这个例子中使用Thread.Sleep()是可以的,还是一般不使用Thread.Sleep()会发生什么并发症?有其他选择吗?


使用Thread.Sleep是否有问题?通常不会,如果您真的想挂起线程。但在这种情况下,您不希望挂起线程,而是希望挂起任务。

因此,在这种情况下,您应该使用:

1
await Task.Delay(minDuration);

这不会挂起整个线程,而是只挂起要挂起的单个任务。同一线程上的所有其他任务都可以继续运行。


我使用Task.Delay而不是Thread.Sleep的一个原因是,您可以将CancellationToken传递给它。如果用户想要StopExecutor,而随机接收的时间跨度很长,那么最终会阻塞很长一段时间。另一方面,在Task.Delay中,您可以取消操作,并将收到取消通知。

我认为你选择的设计还有其他问题。Random类不适合做任务调度程序。我会觉得找到一个ExecuteRandomAsync有点奇怪,因为它主要不是随机执行,而是每隔x分钟执行一些任意的Action

相反,我会用另一种方式。同时保留已经创建的大多数内部构件,但将它们放在不同的类中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class ActionInvoker
{
    private readonly Action _actionToInvoke;

    public ActionInvoker(Action actionToInvoke)
    {
        _actionToInvoke = actionToInvoke;
        _cancellationTokenSource = new CancellationTokenSource();
    }

    private readonly CancellationTokenSource _cancellationTokenSource;
    private Task _executer;

    public void Start(int min, int max, int minDuration)
    {
        if (_executer != null)
        {
            return;
        }

        _executer = Task.Factory.StartNew(
                    async () => await ExecuteRandomAsync(min, max, _actionToInvoke),
                    _cancellationTokenSource.Token, TaskCreationOptions.LongRunning,
                    TaskScheduler.Default)
                    .Unwrap();
    }

    private void Stop()
    {
        try
        {
            _cancellationTokenSource.Cancel();
        }
        catch (OperationCanceledException e)
        {
            // Log the cancellation.
        }
    }

    private async Task ExecuteRandomAsync(int min, int max, Action action)
    {
        Random random = new Random();

        while (!_cancellationTokenSource.IsCancellationRequested)
        {
            await Task.Delay(random.Next(min, max), _cancellationTokenSource.Token);
            action();
        }
    }
}


sleep与要挂起线程的操作系统"对话"。这是一个资源密集型操作,因为线程无论如何都使用RAM(尽管它不需要处理时间)。

通过线程池,您可以使用线程的资源(F.E.RAM)来处理其他一些小任务。为了做到这一点,Windows允许您将线程置于一个特殊的可警报状态,以便它可以被唤醒并临时使用。

因此,Task.Delay允许您将线程置于可警报的睡眠状态,因此允许您使用这些线程的资源,而不需要它们。


这样想。

休眠线程是危险的,因为通常不止一个任务依赖于该线程,更不用说程序的关键组件也可以。不要在线程上使用EDOCX1[1]是不合适的,当它们对您的程序有利时,您将希望使用它们。

由于不可预知的行为,很多人被教导不要睡线。这就像管理一个团队,最终你不得不让一些人去吃午饭,只要其余的人都能正常工作,而你选择去吃午饭的人都出去了,那么你就应该很好,还能继续工作。

如果项目中有人依赖他们的存在,就不要派人去吃午饭。


所有答案都是正确的,我想增加一个实用的观点:

当您有一个远程非实时组件要测试(例如,搜索引擎)时,您需要给它时间,以便在再次引用它进行断言之前进入它的新状态。换句话说,您希望将测试暂停一段时间。由于测试本身的性能是不相关的(请区分测试的性能和组件的性能),您有时更喜欢保持代码(测试的)尽可能简单和直接,并且避免了异步代码的最低复杂性。当然,您可以开始一个新的测试,而不是等待您的组件(这是await Task.Delay()Thread.Sleep()之间的区别),但假设您并不急于进行测试。