Forcing an asyncronous call to a c# method to complete before windows switches threads

Viewed 50

I have a project that uses a Parallel.For call to perform the batch modification of files at once since they all take a considerable amount of time and I want to give feedback as the modification progresses. The entire process also backs up changed files and creates an undo batch command as it goes. The undo file MUST output the undo steps and flush the undo file buffer before moving onto to any other tasks so that all the batch steps (copy original file and delete the copy) get saved before the process of changing the file actually starts. For example... if I am changing two files "A.bin" "B.Bin" I want the batch file to say:

copy "A.original" "A.bin"
delete "A.original"
copy "B.original" "B.bin"
delete "B.original"

The problem is that asyncronous calls can switch between the parallel calls to the method that produces the above output which creates a file with the following output:

copy "A.original" "A.bin"
copy "B.original" "B.bin"
delete "A.original"
delete "B.original"

This creates a situation where if the program crashes or something goes wrong in the process between files during each step, the "undo" script ends up leaving off the "delete" lines making the undo batch leaving junk files which need to be deleted.

Is there a way to mark/force a method or block to be completed before windows switches to another thread? From what I understand about async/await, this has a different use case and will not accomplish what I need (which is the only search results that I can find when I look up how to do something like this online).

Here is the code that actually adds the steps to the batch file. This entire method must be executed in full without a thread switch:

internal static void AddCommit(CommitType type, string sourcePath, string destPath = null)
{
    if ((type & CommitType.RestoreBackup) != 0)
    {
        if (destPath == null)
            throw new ArgumentNullException();
        UndoScript.WriteLine("copy \"" + sourcePath + "\" \"" + destPath + "\" /y");
    }

    if ((type & CommitType.UndoDeleteBackup) != 0)
        UndoScript.WriteLine("delete \"" + sourcePath + "\" /q");

    if ((type & CommitType.CommitDeleteBackup) != 0)
        CommitScript.WriteLine("delete \"" + sourcePath + "\" /q");

    UndoScript.Flush();
    if (CommitScript != null)
        CommitScript.Flush();
}
1 Answers

There's a lot of stuff in your question, and it's hard to get a handle on exactly what you expect for an answer. That said, there's really only one explicit question:

Is there a way to mark/force a method or block to be completed before windows switches to another thread?

And that's easy enough to answer: no.

The whole premise of a pre-emptive multitasking operating system like Windows is that the process has essentially no control over context switching of threads.

The closest you can come is to adjust the thread priority. In some cases that can be similar to controlling the context switching, but even that doesn't give the process complete control, for a couple of reasons:

  1. Thread scheduling includes mitigation for thread-starvation. A thread with a boosting priority will take precedence for some time, but if doing so is starving other threads of CPU time, eventually the OS will still take over and let other threads run.
  2. Threads essentially give up their time-slice when they ask for things that can't be provided right away. The most common example is I/O, which is exactly your scenario. The moment you ask the OS to do something with the disk, that's your thread saying "well, I'm done for now…let me know when you've got what I wanted." No matter what the thread's priority is, the OS will suspend the thread and let a different one run.

Your question doesn't have enough detail to know exactly what the scenario is. But you have at least a couple of options:

  1. If your work has smaller groups of operations that need to be mutually consistent, but do not need to be consistent across all the operations (i.e. with other groups of operations), then just make those groups your granularity for the concurrency. Then you can guarantee ordering of operations within each group.
  2. If all of the operations are together supposed to be mutually consistent and you still want them to be treated concurrently, then treat then as a "transaction". This is common for concurrency scenarios where a failure that might occur would leave the system in an inconsistent/corrupted state. Specifically: add some kind of state, such as a file serving as a marker, that indicates that the operation has started, and remove the state only when the entire operation has completed.

The second option, in your example, would probably involve doing all the copies as a single transaction, and then doing all the file deletions, as a second transaction, only when that first transaction has completed. That way a failure during the copying operation will leave the system in a state from which you can recover (either continue the copying, or rolling back to the the previous state), and a failure during the deletion operation similarly can be handled.

Finally, I will point out that if all of your file operations are occurring on the same device, there may be little or no benefit to using concurrency anyway. Depending on what the code is actually doing, a single thread might be able to keep the device nearly as busy as two or more threads would. After all, even an SSD is way slower than the CPU. Adding more threads might just be creating a situation where all the threads are fighting with each other over the one device, producing no speed-up. In some cases, it might even slow things down.

So the best solution might be to just abandon the whole concurrency aspect altogether. You should demonstrate to yourself that the measured performance of the multi-threaded solution is in fact significantly enough faster than the single-threaded solution to justify the extra complexity.

Related