簡體   English   中英

只有當可選的主線程任務和工作線程完成時,我如何保證代碼的執行?

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

背景:

我正在開發一個應用程序,它處理另一個應用程序的大量插件。 一個如果它的主要用途是安全地修改具有較少記錄的文件中的文件記錄,以便它們可以被視為一個文件(幾乎就像它將文件組合成一組記錄一樣。為了安全地做到這一點,它會跟蹤重要的有關這些文件和對它們所做的更改的信息,以便在它們沒有按預期工作時可以撤消這些更改。

當我的應用程序啟動時,它會分析這些文件並將基本屬性保存在緩存中(以減少加載時間)。 如果緩存中缺少文件,則檢索最重要的內容,然后后台工作人員必須處理該文件以獲取更多信息。 如果之前修改過的文件已更新為文件的新版本,則 UI 必須與用戶確認這一點並刪除其修改數據。 所有這些信息,包括有關其修改的信息,都存儲在緩存中。

我的問題:

我的問題是這些進程都不能保證運行(確認 window 或后台文件處理器)。 如果其中任何一個運行,則緩存必須由主線程更新。 我對工作線程以及哪個線程運行 BackgroundWorker.RunWorkerCompleted 事件處理程序了解不夠,以便有效地決定如何確保緩存更新程序在一個(或兩個)進程完成后運行。

總結一下:如果任一進程運行,它們都必須完成並(可能)等待另一個進程完成,然后再運行緩存更新代碼。 我怎樣才能做到這一點?

附加信息(我目前的干預似乎效果不佳):

我在 RunWorkerCompleted 處理程序中有一行,它一直等到表單引用為 null 后再繼續並退出,但這可能是一個錯誤,因為它有時會鎖定我的程序。

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 function來等待它們完成,所以我使用了
if(count.decrementAndGet() == 0) {
    lock(lk) {
        runnable = true;
        Monitor.Pulse(lk);
    }
}
2.當有任務完成時,我們需要倒計時,如果倒計時到零(這意味着所有的任務都完成了)我們需要通知阻塞線程喚醒並處理任務
if(count.decrementAndGet() == 0) { lock(lk) { runnable = true; Monitor.Pulse(lk); } }

讓我們閱讀更多關於volatile的信息,謝謝

雖然 dung ta van 的“CountDownTask”答案並不是我所需要的,但它極大地啟發了下面的解決方案(有關更多信息,請參閱它)。 基本上我所做的只是添加了一些額外的功能,最重要的是:讓每個任務對結果“投票”(真或假)。 謝謝糞大面包車!

公平地說,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 一樣簡單,然后讓每個影響結果的線程投票。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM