1

I want to know what the best way to process the results of a long-running process serially but on a low-priority thread using the .NET 4.0 Parallel Extensions.

I have the following class that both does the execution and provides the results:

public class Step 
{
  public string Name { get; set; }
  public TimeSpan Duration { get; set; }
  public bool Completed { get; set; }

  public void Execute() 
  {
     DateTime before = DateTime.Now;
     RemotedService.DoSomeStuff();
     Duration = DateTime.Now - before;
     Completed = true;
  }
}

How can I process these steps and also save them to a file, in order, after they are processed? I would like to save them to a file while RemotedService.DoSomeStuff() is waiting for a response from the server.

Writing to the file would be like this:

using (StreamWriter w = File.AppendText("myFile.txt"))
{
  var completedStep = completedSteps.Dequeue();
  w.WriteLine(string.Format("Completed {0} with result: {1} and duration {2}", 
                            completedStep.Name, 
                            completedStep.Completed, 
                            completedStep.Duration));
}

One option that comes to mind is to add them to a Queue and have a Timer that processes them. But this doesn't take advantage of the downtime of the remoted calls.

Another option that comes to mind is to asynchronously write each result to the file using a System.Threading.Tasks.Task per Step, but that doesn't guarantee that they will be saved in order and may also introduce contention with writing to the file.

Michael Hedgpeth
  • 7,732
  • 10
  • 47
  • 66

3 Answers3

1

I would suggest creating a BlockingCollection<Step> (see System.Collections.Concurrent namespace). As each step is completed, it's added to that collection. The default behavior of BlockingCollection is to function as a queue, so you'll get the FIFO behavior you're looking for.

A second thread services the queue, removing each item and writing it to a log file.

So, if you added the queue:

static private BlockingCollection<Step> LogQueue = new BlockingCollection<Step>();

You would add this to your Execute method, after the item is completed:

LogQueue.Add(this);

And your logging thread, which you would start in the static constructor:

static void LoggingThread()
{
    using (var w = new StreamWriter(...))
    {
        while (!LogQueue.IsCompleted())
        {
            Step item;
            if (LogQueue.TryTake(out item))
            {
                w.WriteLine(....);
            }
        }
    }
}

The logging thread as I've written it uses a System.Threading thread. There might be an easier or better way to do it with TPL. I'm not yet terribly familiar with TPL, so I couldn't say.

Jim Mischel
  • 131,090
  • 20
  • 188
  • 351
  • Forgive my ignorance with threading, but I would worry that the background thread would needlessly process the while loop when the Queue is empty, thus slowing down the active thead while it was actually doing work. Is this not the case? – Michael Hedgpeth Nov 23 '10 at 11:03
  • In other words, on the background thread, LogQueue.IsCompleted() would be called over and over and over. I've seen a Thread.Sleep() in the middle of a while loop like this to avoid that, but I know that's bad. – Michael Hedgpeth Nov 23 '10 at 11:05
  • 3
    @Michael: All of the waits in `BlockingCollection` are non-busy waits. They depend on events. So when a thread is waiting on the queue (i.e. in `TryAdd` or `TryTake`), the thread is idle. Not taking any CPU. So in the specific case you mention, `LogQueue.IsCompleted()` returns `false`, and then the call to `TryTake` will either return an item immediately, or wait on an event that is triggered when an item is added to the queue. – Jim Mischel Nov 23 '10 at 13:21
1

One approach is to create a custom Task Scheduler (see http://msdn.microsoft.com/en-us/library/ee789351.aspx). Your custom task scheduler can limit concurrency to one-at-a-time and enforce strict in-order execution.

Creating a task scheduler also allows you to control the thread priority, or the ApartmentState which may be desirable in some situations.

Ian Mercer
  • 38,490
  • 8
  • 97
  • 133
0

You're literally describing the use case for Workflow Foundation - it does all of this stuff for you :)

Ana Betts
  • 73,868
  • 16
  • 141
  • 209
  • I'm not sure I can justify introducing that dependency to my project just so I can save serially in the background. I hadn't thought of how closely my problem domain aligns with WF though. Thanks for enlightening me! – Michael Hedgpeth Nov 23 '10 at 11:02