Пишемо першу багатониткову програму на С#

15 хв. читання

Написати багатониткову програму не так складно, як це було раніше. .NET зробив за нас багато брудної роботи, щоб ми могли більше зосередитись на деталях.

Існує кілька способів створювати багатониткові програми на C#.

Запуск нитки

public class SimpleThreadExample
{
    public void StartMultipleThread()
    {
        DateTime startTime = DateTime.Now;

        Thread t1 = new Thread(() =>
        {
            int numberOfSeconds = 0;
            while (numberOfSeconds < 5)
            {
                Thread.Sleep(1000);

                numberOfSeconds++;
            }

            Console.WriteLine("I ran for 5 seconds");
        });

        Thread t2 = new Thread(() =>
        {
            int numberOfSeconds = 0;
            while (numberOfSeconds < 8)
            {
                Thread.Sleep(1000);

                numberOfSeconds++;
            }

            Console.WriteLine("I ran for 8 seconds");
        });


        //параметризована нитка
        Thread t3 = new Thread(p =>
        {
            int numberOfSeconds = 0;
            while (numberOfSeconds < Convert.ToInt32(p))
            {
                Thread.Sleep(1000);

                numberOfSeconds++;
            }

            Console.WriteLine("I ran for {0} seconds", numberOfSeconds);
        });

        t1.Start();
        t2.Start();
        //передавання параметра параметризованій нитці
        t3.Start(20);

        //чекаємо закінчення t1
        t1.Join();

        //чекаємо закінчення t2
        t2.Join();

        //чекаємо закінчення t3
        t3.Join();


        Console.WriteLine("All Threads Exited in {0} secods", (DateTime.Now - startTime).TotalSeconds);
    }

Переривання нитки

Метод Thread.Abort() знищує нитку. На практиці краще дозволити CLR зробити брудну роботу за вас. Ви можете додати індикатор умови, який буде переривати довготривалий процес.

public class DestroyThreadExample
{
    public bool IsCancelled { get; set; }

    public Thread MyThread { get; set; }

    public void StartThread()
    {
        MyThread = new Thread(() =>
        {
            int numberOfSeconds = 0;
            while (numberOfSeconds < 8)
            {
                if (IsCancelled == false)
                {
                    break;
                }

                Thread.Sleep(1000);

                numberOfSeconds++;
            }

            Console.WriteLine("I ran for {0} seconds", numberOfSeconds);
        });
    }

    //Destroy thread
    public void Abort()
    {
        MyThread.Abort();
    }

    //Graceful exit
    public void GracefulAbort()
    {
        IsCancelled = true;
    }
}

Гнучкий інтерфейс користувача

Один із найпопулярніших варіантів використання ниток – зробити інтерфейс користувача гнучким. Ось як ми можемо це досягти за допомогою ниток та Windows Forms.

public partial class Form1 : Form
{
    public delegate void UpdateLabel(string label);

    public bool IsCancelled { get; set; }

    public Form1()
    {
        InitializeComponent();
    }

    private void UpdateUI(string labelText)
    {
        lblStopWatch.Text = labelText;
    }

    private void btnStart_Click(object sender, EventArgs e)
    {
        DateTime startTime = DateTime.Now;

        IsCancelled = false;

        Thread t = new Thread(() =>
        {
            while (IsCancelled==false)
            {
                Thread.Sleep(1000);

                string timeElapsedInstring = (DateTime.Now - startTime).ToString(@"hh\\:mm\\:ss");

                lblStopWatch.Invoke(new UpdateLabel(UpdateUI), timeElapsedInstring);
                
            }
        });

        t.Start();
    }

    private void btnStop_Click(object sender, EventArgs e)
    {
        IsCancelled = true;
    }
}

Ви помітили, що для оновлення інтерфейсу користувача використовується делегат?

lblStopWatch.Invoke(new UpdateLabel(UpdateUI), timeElapsedInstring);

Це відбувається тому, що ви не можете оновлювати інтерфейс користувача з будь-якої іншої нитки, відмінної від нитки користувацького інтерфейсу. Я думаю, що це дуже поширений сценарій, який можна було б розглянути під час роботи з Windows Forms.

BackgroundWorker

Ще один спосіб створення ниток спеціально для оновлення інтерфейсу користувача – BackgroundWorker.

public partial class Form2 : Form
{
    BackgroundWorker workerThread = null;

    bool _keepRunning = false;

    public Form2()
    {
        InitializeComponent();

        InstantiateWorkerThread();
    }

    private void InstantiateWorkerThread()
    {
        workerThread = new BackgroundWorker();
        workerThread.ProgressChanged += WorkerThread_ProgressChanged;
        workerThread.DoWork += WorkerThread_DoWork;
        workerThread.RunWorkerCompleted += WorkerThread_RunWorkerCompleted;
        workerThread.WorkerReportsProgress = true;
        workerThread.WorkerSupportsCancellation = true;
    }

    private void WorkerThread_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        lblStopWatch.Text = e.UserState.ToString();
    }

    private void WorkerThread_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if(e.Cancelled)
        {
            lblStopWatch.Text = "Cancelled";
        }
        else
        {
            lblStopWatch.Text = "Stopped";
        }            
    }

    private void WorkerThread_DoWork(object sender, DoWorkEventArgs e)
    {
        DateTime startTime = DateTime.Now;

        _keepRunning = true;

        while (_keepRunning)
        {
            Thread.Sleep(1000);
           
            string timeElapsedInstring = (DateTime.Now - startTime).ToString(@"hh\\:mm\\:ss");

            workerThread.ReportProgress(0, timeElapsedInstring);     
           
            if(workerThread.CancellationPending)
            {
                // this is important as it set the cancelled property of RunWorkerCompletedEventArgs to true
                e.Cancel = true;
                break;
            }
        }
    }

    private void btnStart_Click(object sender, EventArgs e)
    {
        workerThread.RunWorkerAsync();
    }

    private void btnStop_Click(object sender, EventArgs e)
    {
        _keepRunning = false;
    }

    private void btnCancel_Click(object sender, EventArgs e)
    {
        workerThread.CancelAsync();          
    }
}

Замість створення простої нитки та використання делегату для оновлення інтерфейсу, у нас є компонент BackgroundWorker, який працює за нас. Він підтримує декілька подій: запуск довготривалого процесу (DoWork), оновлення інтерфейсу користувача (ProgressChanged), також ви можете дізнатися, коли робота фонової нитки закінчилася (RunWorkerCompleted). У старій нитці, знаючи, що кінець нитки важко розпізнати, вам слід використати будь-що із Thread.Join або інші дескриптори очікування.

Проблеми, пов'язані з нитками

Під час написання багатониткової програми, існує безліч відомих проблем, які ми повинні вміти обробляти. Взаємне блокування, порушення послідовного доступу (стану гонитви) – лише мінімум. Необхідно підтримувати синхронізацію доступу до різних ресурсів, щоб бути впевненим в тому, що вихідний результат не змінюється. Якщо файл у файловій системі використовується кількома нитками, програма повинна дозволити лише одній нитці змінювати файл у певний час, інакше файл пошкодиться. Одним зі способів такого обмеження є використання Lock Keyword. Якщо ми отримуємо доступ до спільного ресурсу поза оголошенням Lock, це дозволить виконання коду лише однієї нитки в рамках блоку lock.

public class LockExample
{

    public int SharedResource { get; set; }

    public object _locker = 0;

    public void StartThreadAccessingSharedResource()
    {
        Thread t1 = new Thread(() =>
        {
            lock (_locker)
            {
                SharedResource++;
            }

        });

        Thread t2 = new Thread(() =>
        {
            lock (_locker)
            {
                SharedResource--;
            }

        });

        t1.Start();
        t2.Start();
    }
}

У цьому прикладі ми маємо дві нитки, що мають доступ до одного й того ж ресурсу. Використання ключового слова блокування гарантуватиме, що для спільної змінної буде доступ до однієї нитки за раз. Поки нитка t1 виконує код усередині блоку lock, нитка t2 буде чекати.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.5K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація