【问题标题】:Concurrent Tasks and different cursor positions for each of them并发任务和每个任务的不同光标位置
【发布时间】:2019-01-30 06:46:25
【问题描述】:

我开始练习Tasks,我尝试了以下代码:

static void Main()
{
    Task.Factory.StartNew(() =>
    {
        Write('a', 0);
    });

    var t = new Task(() =>
    {
        Write('b', 10);
    });

    t.Start();

    Write('c', 20);
    Console.ReadLine();
}

static void Write(char c, int x)
{
    int yCounter = 0;
    for (int i = 0; i < 1000; i++)
    {
        Console.WriteLine(c);
        Console.SetCursorPosition(x, yCounter);
        yCounter++;
        Thread.Sleep(100);
    }
}

我的想法是查看控制台如何在三个不同的列之间切换以输出不同的字符。它确实交换了列,但它不输出正确的字符。例如,在第一列中它只需要输出'a',但它也输出'b'和'c',其他2列也是如此。

【问题讨论】:

    标签: c# task


    【解决方案1】:

    这可能是使用任务的一个特别糟糕的例子——或者是一个如何糟糕地使用任务的例子。

    在您的任务中,您正在设置一个全局状态 (SetCursorPosition),这当然会影响其他任务(Console 毕竟是静态的)。有可能

    Console.WriteLine('b')
    

    在光标设置为01020 后调用,其他值反之亦然。任务不应依赖于任何可能已更改的全局(或类级别)状态(除非值可能已更改的任务可以接受)。关于您的示例,您必须确保在编写输出之前没有其他任务调用SetCursorPosition。实现这一点的最简单方法是锁定任务

    private static object lockObject = new object(); // you need an object of a reference type for locking
    
    static void Write(char c, int x)
    {
        int yCounter = 0;
        for (int i = 0; i < 1000; i++)
        {
            lock(lockObject)
            {
                Console.SetCursorPosition(x, yCounter);
                Console.Write(c);
            }
    
            yCounter++;
            Thread.Sleep(100);
        }
    }
    

    lock 确保没有两个任务同时进入块(假设锁定对象非常相同),因此每个任务都可以将光标设置到它想要的位置在没有任何其他任务的情况下写入并写入其字符,将光标设置到任何其他位置。 (另外,我已经交换了WriteSetCursorPosition,因为我们必须在写入输出之前调用SetCursorPosition - 无论如何,如果不交换这两行,锁将毫无用处。)

    【讨论】:

    • 谢谢!我知道这可能是使用任务的一种不好的方式,我只是想更多地了解它们并提出有趣的例子:) 但我理解你的意思,我以前听说过锁,我认为它可能是有关的!谢谢!
    • 它工作正常,但是我注意到它总是以完全相同的顺序执行任务,总是首先是'c',然后是'a',然后是'b',因为这总是发生,这一定是有原因的,而不仅仅是一个随机的过程。你知道原因吗? (我在外观中移动了 thread.sleep,这就是我看到执行任务的顺序的方式)
    【解决方案2】:

    除了保罗的回答。

    如果您正在处理任务和async/await,请不要以任何方式混合TaskThread

    使用Task.Run/Task.Start 执行您的Write 方法称为“异步同步”。这是一种不好的做法,应该避免。

    这是您的代码,以异步方式重写,带有异步同步:

    class Program
    {
        static void Main(string[] args)
        {
            var asyncLock = new AsyncLock();
    
            // we need ToList here, since IEnumerable is lazy, 
            // and must be enumerated to produce values (tasks in this case);
            // WriteAsync call inside Select produces a "hot" task - task, that is already scheduled;
            // there's no need to start hot tasks explicitly - they are already started
            new[] { ('a', 0), ('b', 10), ('c', 20) }
                .Select(_ => WriteAsync(_.Item1, _.Item2, asyncLock))
                .ToList();
    
            Console.ReadLine();
        }
    
        static async Task WriteAsync(char c, int x, AsyncLock asyncLock)
        {
            for (var i = 0; i < 1000; i++)
            {
                using (await asyncLock.LockAsync())
                {
                    Console.SetCursorPosition(x, i);
                    Console.Write(c);
                }
    
                await Task.Delay(100);
            }
        }
    }
    

    AsyncLock 位于 Nito.AsyncEx 包中。

    【讨论】:

      猜你喜欢
      • 2012-02-28
      • 1970-01-01
      • 2023-03-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多