Home ...

CSharp parallel programming without blocking 2

Ragavendra B.N.
Ragavendra B.N.

In my last post I briefly went over parallel programming initial steps but did not get into the nitty gritty as one would expect. No doubt Microsoft's article about it is great. Still as humans I think it is always better to read more than one book to understand and process the information correctly.

With the current .net6 LTS version the C# 10 is used as of this writing

That's that for the introduction. I have segregated this article into three different ways of performing it.

Before continuing any further please note that almost all ways should work when the main thread is running before and after the snippet is completed execution like say in an ASP .Net server instance. There were issues running them in a Console type or instance as the main thread finished completion all others will end or similar.

Also, important to note that if TaskScheduler.Current == TaskScheduler.Default, then the main thread will not wait for the sub threads. One can add a simple check like below.

  if(TaskScheduler.Current == TaskScheduler.Default){
    Console.WriteLine("Main will not wait!");
  }
  else {
    Console.WriteLine("Main will wait!");
  }

Using Parallel.ForEachAsync

This is the easiest way to do it. I a using the ReadLinesAsync for this demo. Notice how the last write line is printed first demonstrating the asynchronous operation.

public async Task Load() {
  var cncl = new CancellationTokenSource();
  var res = System.IO.File.ReadLinesAsync("/tmp/file.txt", System.Text.Encoding.UTF8, cncl.Token);

  // this is the only way I was able to run this bit after the main thread exited
  Parallel.ForEachAsync(res, (line, cncl) =>
  {
      Console.WriteLine("Line is " + line);
      return default;
  });

   Console.WriteLine("Main finished now.");
}

Using Threads

This is somewhat the next easiest way to do it. Make sure the thread's IsBackground is set to false otherwise say if the Load() dies after calling Start(), Start() will not complete to execute. Read more here .

public async Task Load() {

  // this worked nicely, with thr running even after server pocess was killed
  var th = new ThreadStart(() => {
      Thread.Sleep(10_000);
      Console.WriteLine("Thr finished now.");
  });
  var thr = new Thread(th);
  thr.IsBackground = false;
  thr.Start();

  Console.WriteLine("Main finished now.");
}

Using Task.Run()

This is easy as well but with a lot of options like using the TaskContinuationOptions . This is the recommended approach that I would suggest, say like using TaskContinuationOptions.OnlyOnFaulted to catch any exceptions without needing to use the try - catch at all. This is similar to the Promise syntax in Javascript and other languages. If one has used it in any of those languages, it should be fairly an easy pop to gulp if I may.

public async Task Load() {

  async void Load_()
  {
      var res = System.IO.File.ReadLinesAsync("/tmp/file.txt", System.Text.Encoding.UTF8, cancellationToken.Token);

      await foreach (var line in res)
      {
          Console.WriteLine("Line is " + line);
      }
  }

  Task.Run(() => Load_())
  .ContinueWith(ts => {
      _logger.LogInformation("Finished loading now ...");
      throw new ApplicationException("Some app excpn here ...");
  // }, TaskContinuationOptions.OnlyOnRanToCompletion)
  })
  .ContinueWith(ex => {
      _logger.LogError("Issue loading ... " + ex.Exception.Message);
  }, TaskContinuationOptions.OnlyOnFaulted);

  Console.WriteLine("Main finished now.");
}