简体   繁体   中英

How do I guarantee execution of code only if and when optional main thread task and worker threads are finished?

Background:

I have an application I am developing that deals with a large number of addons for another application. One if its primary uses is to safely modify file records in files with fewer records so that they may be treated as one file (almost as if it is combing the files together into one set of records. To do this safely it keeps track of vital information about those files and changes made to them so that those changes can be undone if they don't work as expected.

When my application starts, it analyzes those files and keeps essential properties in a cache (to reduce load times). If a file is missing from the cache, the most important stuff is retrieved and then a background worker must process the file for more information. If a file that was previously modified has been updated with a new version of the file, the UI must confirm this with the user and its modification data removed. All of this information, including information on its modification is stored in the cache.

My Problem:

My problem is that neither of these processes are guaranteed to run (the confirmation window or the background file processor). If either of them run, then the cache must be updated by the main thread. I don't know enough about worker threads, and which thread runs the BackgroundWorker.RunWorkerCompleted event handler in order to effectively decide how to approach guaranteeing that the cache updater is run after either (or both) processes are completed.

To sum up: if either process is run, they both must finish and (potentially) wait for the other to be completed before running the cache update code. How can I do this?

ADJUNCT INFO (My current intervention that doesn't seem to work very well):

I have a line in the RunWorkerCompleted handler that waits until the form reference is null before continuing and exiting but maybe this was a mistake as it sometimes locks my program up.

SpinWait.SpinUntil(() => overwriteForm == null);

I haven't included any more code because I anticipate that this is more of a conceptual question than a code one. However, if necessary, I can supply code if it helps.

I think CountDownTask is what you need

using System;
using System.Threading;

public class Program
{

    public class AtomicInteger
    {
        protected int value = 0;

        public AtomicInteger(int value)
        {
            this.value = value;
        }

        public int DecrementAndGet()
        {
            int answer = Interlocked.Decrement(ref value);
            return answer;
        }
    }

    public interface Runnable
    {
        void Run();
    }

    public class CountDownTask
    {
        private AtomicInteger count;
        private Runnable task;
        private Object lk = new Object();
        private volatile bool runnable;
        private bool cancelled;

        public CountDownTask(Int32 count, Runnable task)
        {
            this.count = new AtomicInteger(count);
            this.task = task;
            this.runnable = false;
            this.cancelled = false;
        }

        public void CountDown()
        {
            if (count.DecrementAndGet() == 0)
            {
                lock (lk)
                {
                    runnable = true;
                    Monitor.Pulse(lk);
                }
            }
        }

        public void Await()
        {
            lock (lk)
            {
                while (!runnable)
                {
                    Monitor.Wait(lk);
                }
                if (cancelled)
                {
                    Console.WriteLine("Sorry! I was cancelled");
                }
                else {
                    task.Run();
                }
            }
        }

        public void Cancel()
        {
            lock (lk)
            {
                runnable = true;
                cancelled = true;
                Monitor.Pulse(lk);
            }
        }
    }

    public class HelloWorldTask : Runnable
    {
        public void Run()
        {
            Console.WriteLine("Hello World, I'm last one");
        }
    }

    public static void Main()
    {
        Thread.CurrentThread.Name = "Main";
        Console.WriteLine("Current Thread: " + Thread.CurrentThread.Name);
        CountDownTask countDownTask = new CountDownTask(3, new HelloWorldTask());
        Thread worker1 = new Thread(() => {
            Console.WriteLine("Worker 1 run");
            countDownTask.CountDown();
        });
        Thread worker2 = new Thread(() => {
            Console.WriteLine("Worker 2 run");
            countDownTask.CountDown();
        });
        Thread lastThread = new Thread(() => countDownTask.Await());
        lastThread.Start();
        worker1.Start();
        worker2.Start();
        //countDownTask.Cancel();
        Console.WriteLine("Main Thread Run");
        countDownTask.CountDown();
        Thread.Sleep(1000);
    }
}

let me explain (but you can refer Java CountDownLatch )

1. To ensure a task must run after another tasks, we need create a Wait function to wait for they done, so I used
if(count.decrementAndGet() == 0) {
    lock(lk) {
        runnable = true;
        Monitor.Pulse(lk);
    }
}
2. When there is a task done, we need count down, and if count down to zero (it means all of the tasks was done) we will need notify to blocked thread to wake up and process task
if(count.decrementAndGet() == 0) { lock(lk) { runnable = true; Monitor.Pulse(lk); } }

Let read more about volatile , thanks

While dung ta van's "CountDownTask" answer isn't quite what I needed, it heavily inspired the solution below (see it for more info). Basically all I did was add some extra functionality and most importantly: made it so that each task "vote" on the outcome (true or false). Thanks dung ta van!

To be fair, dung ta van's solution DOES work to guarantee execution which as it turns out isn't quite what I needed. My solution adds the ability to make that execution conditional.

This was my solution which worked:

public enum PendingBool
{
    Unknown = -1,
    False,
    True
}
public interface IRunnableTask
{
    void Run();
}

public class AtomicInteger
{
    int integer;
    public int Value { get { return integer; } }
    public AtomicInteger(int value) { integer = value; }
    public int Decrement() { return Interlocked.Decrement(ref integer); }
    public static implicit operator int(AtomicInteger ai) { return ai.integer; }
}

public class TaskElectionEventArgs
{
    public bool VoteResult { get; private set; }
    public TaskElectionEventArgs(bool vote) { VoteResult = vote; }
}

public delegate void VoteEventHandler(object sender, TaskElectionEventArgs e); 

public class SingleVoteTask
{
    private AtomicInteger votesLeft;
    private IRunnableTask task;
    private volatile bool runTask = false;
    private object _lock = new object();

    public event VoteEventHandler VoteCast;
    public event VoteEventHandler TaskCompleted;

    public bool IsWaiting { get { return votesLeft.Value > 0; } }
    
    public PendingBool Result 
    {
        get
        {
            if (votesLeft > 0)
                return PendingBool.Unknown;
            else if (runTask)
                return PendingBool.True;
            else
                return PendingBool.False;
        }
    }

    public SingleVoteTask(int numberOfVotes, IRunnableTask taskToRun) 
    {
        votesLeft = new AtomicInteger(numberOfVotes);
        task = taskToRun; 
    }

    public void CastVote(bool vote)
    {
        votesLeft.Decrement();
        runTask |= vote;
        VoteCast?.Invoke(this, new TaskElectionEventArgs(vote));
        if (votesLeft == 0)
            lock (_lock)
            {
                Monitor.Pulse(_lock);
            }
    }

    public void Await()
    {
        lock(_lock)
        {
            while (votesLeft > 0)
                Monitor.Wait(_lock);
            if (runTask)
                task.Run();
            TaskCompleted?.Invoke(this, new TaskElectionEventArgs(runTask));
        }
    }
}

Implementing the above solution was as simple as creating the SingleVoteTask in the UI thread and then having each thread affecting the outcome cast a vote.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM