如何保证只有当可选的主线程任务和工作线程完成时才执行代码?

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

背景:

我正在开发一个应用程序,它处理另一个应用程序的大量插件。如果它的主要用途是安全地修改记录较少的文件中的文件记录,以便它们可以被视为一个文件(几乎就像将文件组合成一组记录一样)。为了安全地做到这一点,它会跟踪重要的有关这些文件和对它们所做更改的信息,以便在它们未按预期工作时可以撤消这些更改。

当我的应用程序启动时,它会分析这些文件并将基本属性保存在缓存中(以减少加载时间)。如果缓存中缺少文件,则会检索最重要的内容,然后后台工作人员必须处理该文件以获取更多信息。如果以前修改过的文件已更新为文件的新版本,UI 必须与用户确认这一点并删除其修改数据。所有这些信息,包括有关其修改的信息都存储在缓存中。

我的问题:

我的问题是这两个进程都不能保证运行(确认window 或后台文件处理器)。如果其中任何一个运行,那么缓存必须由主线程更新。我不太了解工作线程,哪个线程 运行 是 BackgroundWorker.RunWorkerCompleted 事件处理程序,以便有效地决定如何保证缓存更新程序在任一(或both) 进程都完成了。

总结一下:如果任一进程是 运行,它们都必须完成并(可能)等待另一个完成,然后才能 运行 缓存更新代码。我该怎么做?

ADJUNCT INFO(我目前的干预似乎效果不是很好):

我在 RunWorkerCompleted 处理程序中有一行等待表单引用为空,然后继续并退出,但这可能是一个错误,因为它有时会锁定我的程序。

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

我没有包含更多代码,因为我预计这是一个概念性问题,而不是代码问题。但是,如有必要,如果有帮助,我可以提供代码。

我觉得CountDownTask是你需要的

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);
    }
}

让我解释一下(但你可以参考Java CountDownLatch

1.为了确保一个任务必须运行在另一个任务之后,我们需要创建一个Wait函数来等待它们完成,所以我使用
while(!runnable) {
    Monitor.Wait(lk);
}
2.当有任务完成时,我们需要倒计时,如果倒计时为零(这意味着所有任务都完成了)我们需要通知阻塞线程唤醒并处理任务
if(count.decrementAndGet() == 0) {
    lock(lk) {
        runnable = true;
        Monitor.Pulse(lk);
    }
}

让我们详细了解 volatile,谢谢

虽然 dung ta van 的“CountDownTask”答案不是我所需要的,但它极大地启发了下面的解决方案(有关详细信息,请参阅它)。基本上我所做的就是添加一些额外的功能,最重要的是:让每个任务都对结果(真或假)进行“投票”。谢谢 dung ta van!

公平地说,dung ta van 的解决方案确实可以保证执行,但事实证明这并不是我所需要的。我的解决方案增加了使执行有条件的能力。

这是我的有效解决方案:

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));
        }
    }
}

实施上述解决方案非常简单,只需在 UI 线程中创建 SingleVoteTask,然后让影响结果的每个线程进行投票。