Exercise Notes

Learning goals

  • Software design approaches and patterns, to identify reusable solutions to commonly occurring problems
  • Apply an appropriate software development approach according to the relevant paradigm (for example object oriented, event driven or procedural)

Programs used

Starting code

Create a new console application with a git repository (as you did in the BusBoard exercise). Include the code below in an appropriate place.

private class DataStore { public int Value { get; set; } }
 
private DataStore store = new DataStore();
 
public void ConcurrencyTest()
{
  var thread1 = new Thread(IncrementTheValue);
  var thread2 = new Thread(IncrementTheValue);
 
  thread1.Start();
  thread2.Start();
 
  thread1.Join(); // Wait for the thread to finish executing
  thread2.Join();
 
  Console.WriteLine($"Final value: {store.Value}");
}
 
private void IncrementTheValue()
{
  store.Value++;
}

Part 1 – Dodgy Counter

  1. Start from the code above.
  2. Run the two threads simultaneously and see the result. It will probably print 2 because both increments have run correctly.
  3. Run your code 100,000 times (using a loop, not manually!) to check that it does sometimes only sets count to 1 and not 2, even though it was called twice. I recommend only printing count if it’s 1 as most of the time it will be 2.
  4. Add a lock and check that it fixes the problem.
  5. Add some timing code to time how long your algorithm takes with or without the lock. You should find that adding the lock makes your code take longer, though the difference probably won’t be massive!

What to do if it never prints 1?

It’s possible that your CPU handles threads very well and that you never see a 1. In this case you can slow down your increment method slightly to let the threads interleave.

var newCount = store.Value + 1;
Thread.Sleep(1);
store.Value = newCount;

This should make the interleaving happen every time, so you shouldn’t need to run the code 100,000 times anymore!

Part 2 – Deadlocks

In Part 1, the code uses a DataStore class to demonstrate the need for locking in concurrent code. However, where you can have locks you can also have deadlocks. Introduce a second instance of DataStore into this code, and write code that will (sometimes) produce a deadlock.

Part 3 – Dictionaries in multithreading

The text asserts “In multithreaded code the standard dictionary paradigm of test whether a value is in the dictionary; if not, then add it isn’t safe”. Why not?

Part 4 – Await

The CountNonExistentWordsAsync method below is asynchronous (non-blocking), but if the caller awaits the result of this method then execution may still end up being blocked for longer than really necessary. What further changes could you make to this method to improve this situation?

public async Task<int> CountNonExistentWordsAsync()
{
  Task<string> articleTask = new WebClient().DownloadStringTaskAsync(
    @"https://msdn.microsoft.com/en-gb/library/mt674882.aspx");
  Task<string> wordsTask = new WebClient().DownloadStringTaskAsync(
    @"https://github.com/dwyl/english-words");
 
  string article = await articleTask;
  string words = await wordsTask;
 
  HashSet<string> wordList = new HashSet<string>(words.Split('\n'));
 
  var nonExistentWords = 0;
 
  foreach (string word in article.Split('\n', ' '))
  {
    if (!wordList.Contains(word)) nonExistentWords++;
  }
 
  return nonExistentWords;
}