Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
CSharpNotesForProfessionals.pdf
Скачиваний:
57
Добавлен:
20.05.2023
Размер:
6.12 Mб
Скачать

Chapter 112: Threading

Section 112.1: Avoiding Reading and Writing Data

Simultaneously

Sometimes, you want your threads to simultaneously share data. When this happens it is important to be aware of the code and lock any parts that could go wrong. A simple example of two threads counting is shown below.

Here is some dangerous (incorrect) code:

using System.Threading;

class MainClass

{

static int count { get; set; }

static void Main()

{

for (int i = 1; i <= 2; i++)

{

var thread = new Thread(ThreadMethod); thread.Start(i);

Thread.Sleep(500);

}

}

static void ThreadMethod(object threadNumber)

{

while (true)

{

var temp = count;

System.Console.WriteLine("Thread " + threadNumber + ": Reading the value of count."); Thread.Sleep(1000);

count = temp + 1;

System.Console.WriteLine("Thread " + threadNumber + ": Incrementing the value of count to:" + count);

Thread.Sleep(1000);

}

}

}

You'll notice, instead of counting 1,2,3,4,5... we count 1,1,2,2,3...

To fix this problem, we need to lock the value of count, so that multiple di erent threads cannot read and write to it at the same time. With the addition of a lock and a key, we can prevent the threads from accessing the data simultaneously.

using System.Threading;

class MainClass

{

static int count { get; set; }

static readonly object key = new object();

static void Main()

{

for (int i = 1; i <= 2; i++)

GoalKicker.com – C# Notes for Professionals

569

{

var thread = new Thread(ThreadMethod); thread.Start(i);

Thread.Sleep(500);

}

}

static void ThreadMethod(object threadNumber)

{

while (true)

{

lock (key)

{

var temp = count;

System.Console.WriteLine("Thread " + threadNumber + ": Reading the value of

count.");

Thread.Sleep(1000); count = temp + 1;

System.Console.WriteLine("Thread " + threadNumber + ": Incrementing the value of count to:" + count);

}

Thread.Sleep(1000);

}

}

}

Section 112.2: Creating and Starting a Second Thread

If you're doing multiple long calculations, you can run them at the same time on di erent threads on your computer. To do this, we make a new Thread and have it point to a di erent method.

using System.Threading;

class MainClass { static void Main() {

var thread = new Thread(Secondary); thread.Start();

}

static void Secondary() { System.Console.WriteLine("Hello World!");

}

}

Section 112.3: Parallel.ForEach Loop

If you have a foreach loop that you want to speed up and you don't mind what order the output is in, you can convert it to a parallel foreach loop by doing the following:

using System;

using System.Threading; using System.Threading.Tasks;

public class MainClass {

public static void Main() {

int[] Numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// Single-threaded

Console.WriteLine("Normal foreach loop: ");

GoalKicker.com – C# Notes for Professionals

570

foreach (var number in Numbers) { Console.WriteLine(longCalculation(number));

}

// This is the Parallel (Multi-threaded solution)

Console.WriteLine("Parallel foreach loop: "); Parallel.ForEach(Numbers, number => {

Console.WriteLine(longCalculation(number));

});

}

private static int longCalculation(int number) { Thread.Sleep(1000); // Sleep to simulate a long calculation return number * number;

}

}

Section 112.4: Deadlocks (hold resource and wait)

A deadlock is what occurs when two or more threads are waiting for each other to complete or to release a resource in such a way that they wait forever.

If thread1 holds a lock on resource A and is waiting for resource B to be released while thread2 holds resource B and is waiting for resource A to be released, they are deadlocked.

Clicking button1 for the following example code will cause your application to get into aforementioned deadlocked state and hang

private void button_Click(object sender, EventArgs e)

{

DeadlockWorkers workers = new DeadlockWorkers(); workers.StartThreads();

textBox.Text = workers.GetResult();

}

private class DeadlockWorkers

{

Thread thread1, thread2;

object resourceA = new object(); object resourceB = new object();

string output;

public void StartThreads()

{

thread1 = new Thread(Thread1DoWork); thread2 = new Thread(Thread2DoWork); thread1.Start();

thread2.Start();

}

public string GetResult()

{

thread1.Join(); thread2.Join(); return output;

}

public void Thread1DoWork()

{

GoalKicker.com – C# Notes for Professionals

571

Thread.Sleep(100); lock (resourceA)

{

Thread.Sleep(100); lock (resourceB)

{

output += "T1#";

}

}

}

public void Thread2DoWork()

{

Thread.Sleep(100); lock (resourceB)

{

Thread.Sleep(100); lock (resourceA)

{

output += "T2#";

}

}

}

}

To avoid being deadlocked this way, one can use Monitor.TryEnter(lock_object, timeout_in_milliseconds) to check if a lock is held on an object already. If Monitor.TryEnter does not succeed in acquiring a lock on lock_object before timeout_in_milliseconds, it returns false, giving the thread a chance to release other held resources and yielding, thus giving other threads a chance to complete as in this slightly modified version of the above:

private void button_Click(object sender, EventArgs e)

{

MonitorWorkers workers = new MonitorWorkers(); workers.StartThreads();

textBox.Text = workers.GetResult();

}

private class MonitorWorkers

{

Thread thread1, thread2;

object resourceA = new object(); object resourceB = new object();

string output;

public void StartThreads()

{

thread1 = new Thread(Thread1DoWork); thread2 = new Thread(Thread2DoWork); thread1.Start();

thread2.Start();

}

public string GetResult()

{

thread1.Join(); thread2.Join(); return output;

}

GoalKicker.com – C# Notes for Professionals

572