Параллельные задачи и разные позиции курсора для каждой из них - PullRequest
0 голосов
/ 30 января 2019

Я начинаю практиковаться с Задачами, и я попробовал следующий код:

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 ]

0 голосов
/ 30 января 2019

В дополнение к ответу Пола.

Если вы имеете дело с задачами и async / await, не смешивайте Task и Thread в любом случае.

Выполнение вашего Write метода с использованием Task.Run / Task.Start называется «асинхронная синхронизация».Это плохая практика, и ее следует избегать.

Вот ваш код, переписанный асинхронным способом с асинхронной синхронизацией:

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 пакет.

0 голосов
/ 30 января 2019

Это может быть особенно плохой пример использования задач или пример того, как использовать задачи плохо.

В своих задачах вы устанавливаете глобальное состояние (SetCursorPosition), которое, конечно, повлияет на другие задачи (в конце концов, Console является статическим).Возможно, что

Console.WriteLine('b')

вызывается после того, как курсор был установлен на 0, 10 или 20, и наоборот для других значений.Задачи не должны полагаться на какое-либо глобальное состояние (или уровень класса), которое могло бы измениться (кроме случаев, когда для задачи нормально, что значение могло бы измениться).Что касается вашего примера, вы должны убедиться, что ни одна из других задач не вызовет 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 гарантирует, что никакие две задачи не входят в блок одновременно (учитывая, что объект блокировки является очень одинаковым ), следовательно, каждая задача может установить курсор в положение, в которое она хочет записать, и записать свой символ без каких-либо других задач, устанавливающих курсор в любую другую позицию.(Кроме того, я поменял местами Write и SetCursorPosition, так как перед записью в выходной файл нам нужно будет вызвать SetCursorPosition - блокировка будет бесполезной, если в любом случае эти две строки не поменяются местами.)

...