亚洲乱色熟女一区二区三区丝袜,天堂√中文最新版在线,亚洲精品乱码久久久久久蜜桃图片,香蕉久久久久久av成人,欧美丰满熟妇bbb久久久

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

C#.NET中的多線程超時處理實踐

admin
2023年8月28日 15:2 本文熱度 1639

    最近我正在處理C#中關(guān)于timeout行為的一些bug。解決方案非常有意思,所以我在這里分享給廣大博友們。

我要處理的是下面這些情況:


  • 我們做了一個應(yīng)用程序,程序中有這么一個模塊,它的功能向用戶顯示一個消息對話框,15秒后再自動關(guān)閉該對話框。但是,如果用戶手動關(guān)閉對話框,則在timeout時我們無需做任何處理。


  • 程序中有一個漫長的執(zhí)行操作。如果該操作持續(xù)5秒鐘以上,那么請終止這個操作。


  • 我們的的應(yīng)用程序中有執(zhí)行時間未知的操作。當(dāng)執(zhí)行時間過長時,我們需要顯示一個“進行中”彈出窗口來提示用戶耐心等待。我們無法預(yù)估這次操作會持續(xù)多久,但一般情況下會持續(xù)不到一秒。為了避免彈出窗口一閃而過,我們只想要在1秒后顯示這個彈出窗口。反之,如果在1秒內(nèi)操作完成,則不需要顯示這個彈出窗口。


這些問題是相似的。在超時之后,我們必須執(zhí)行X操作,除非Y在那個時候發(fā)生。


為了找到解決這些問題的辦法,我在試驗過程中創(chuàng)建了一個類:


public class OperationHandler

{

    private IOperation _operation;

    

    public OperationHandler(IOperation operation)

    {

        _operation = operation;

    }    

    public void StartWithTimeout(int timeoutMillis)

    {

        //在超時后需要調(diào)用 "_operation.DoOperation()" 

    }    

    public void StopOperationIfNotStartedYet()

    {

        //在超時期間需要停止"DoOperation" 

    }

}


我的操作類:


public class MyOperation : IOperation

{

    public void DoOperation()

    {

        Console.WriteLine("Operation started");

    }

}

public class MyOperation : IOperation

{

    public void DoOperation()

    {

        Console.WriteLine("Operation started");

    }

}


我的測試程序:


static void Main(string[] args)

{

    var op = new MyOperation();

    var handler = new OperationHandler(op);

    Console.WriteLine("Starting with timeout of 5 seconds");

    handler.StartWithTimeout(5 * 1000);

    Thread.Sleep(6 * 1000);

    

    Console.WriteLine("Starting with timeout of 5 but cancelling after 2 seconds");

    handler.StartWithTimeout(5 * 1000);

    Thread.Sleep(2 * 1000);

    handler.StopOperationIfNotStartedYet();

    

    Thread.Sleep(4 * 1000);

    Console.WriteLine("Finished...");

    Console.ReadLine();

}


結(jié)果應(yīng)該是:



現(xiàn)在我們可以開始試驗了!



解決方案1:在另一個線程上休眠


我最初的計劃是在另一個不同的線程上休眠,同時用一個布爾值來標記Stop是否被調(diào)用。


public class OperationHandler

{

    private IOperation _operation;

    private bool _stopCalled;


    public OperationHandler(IOperation operation)

    {

        _operation = operation;

    }

    public void StartWithTimeout(int timeoutMillis)

    {

        Task.Factory.StartNew(() =>

        {

            _stopCalled = false;

            Thread.Sleep(timeoutMillis);

            if (!_stopCalled)

                _operation.DoOperation();

        });

    }

    public void StopOperationIfNotStartedYet()

    {

        _stopCalled = true;

    }

}


針對正常的線程執(zhí)行步驟,這段代碼運行過程并沒有出現(xiàn)問題,但是總是感覺有些別扭。仔細探究后,我發(fā)現(xiàn)其中有一些貓膩。首先,在超時期間,有一個線程從線程池中取出后什么都沒做,顯然這個線程是被浪費了。


其次,如果程序停止執(zhí)行了,線程會繼續(xù)休眠直到超時結(jié)束,浪費了CPU時間。


但是這些并不是我們這段代碼最糟糕的事情,實際上我們的程序?qū)嵾€存在一個明顯的bug:


如果我們設(shè)置10秒的超時時間,開始操作后,2秒停止,然后在2秒內(nèi)再次開始。


當(dāng)?shù)诙螁訒r,我們的_stopCalled標志將變成false。然后,當(dāng)我們的第一個Thread.Sleep()完成時,即使我們?nèi)∠?,它也會調(diào)用DoOperation。


之后,第二個Thread.Sleep()完成,并將第二次調(diào)用DoOperation。結(jié)果導(dǎo)致DoOperation被調(diào)用兩次,這顯然不是我們所期望的。


如果你每分鐘有100次這樣的超時,我將很難捕捉到這種錯誤。


當(dāng)StopOperationIfNotStartedYet被調(diào)用時,我們需要某種方式來取消DoOperation的調(diào)用。


如果我們嘗試使用計時器呢?



解決方案2:使用計時器


.NET中有三種不同類型的記時器,分別是:


  • System.Windows.Forms命名空間下的Timer控件,它直接繼承自Componet。


  • System.Timers命名空間下的Timer類。


  • System.Threading.Timer類。


這三種計時器中,System.Threading.Timer足以滿足我們的需求。這里是使用Timer的代碼:


public class OperationHandler

{

    private IOperation _operation;

    private Timer _timer;


    public OperationHandler(IOperation operation)

    {

        _operation = operation;

    }

    public void StartWithTimeout(int timeoutMillis)

    {

        if (_timer != null)

            return;


        _timer = new Timer(

            state =>

            {

                _operation.DoOperation();

                DisposeOfTimer();

            }, null, timeoutMillis, timeoutMillis);

    }        

    public void StopOperationIfNotStartedYet()

    {

        DisposeOfTimer();

    }

    private void DisposeOfTimer()

    {

        if (_timer == null)

            return;

        var temp = _timer;

        _timer = null;

        temp.Dispose();

    }

}


執(zhí)行結(jié)果如下:



現(xiàn)在當(dāng)我們停止操作時,定時器被丟棄,這樣就避免了再次執(zhí)行操作。這已經(jīng)實現(xiàn)了我們最初的想法,當(dāng)然還有另一種方式來處理這個問題。


解決方案3:ManualResetEvent或AutoResetEvent


ManualResetEvent/AutoResetEvent的字面意思是手動或自動重置事件。


AutoResetEvent和ManualResetEvent是幫助您處理多線程通信的類。 


基本思想是一個線程可以一直等待,知道另一個線程完成某個操作, 然后等待的線程可以“釋放”并繼續(xù)運行。


ManualResetEvent類和AutoResetEvent類請參閱MSDN:


ManualResetEvent類:

https://msdn.microsoft.com/zh-cn/library/system.threading.manualresetevent.aspx


AutoResetEvent類:

https://msdn.microsoft.com/zh-cn/library/system.threading.autoresetevent.aspx


言歸正傳,在本例中,直到手動重置事件信號出現(xiàn),mre.WaitOne()會一直等待。mre.Set()將標記重置事件信號。 


ManualResetEvent將釋放當(dāng)前正在等待的所有線程。AutoResetEvent將只釋放一個等待的線程,并立即變?yōu)闊o信號。WaitOne()也可以接受超時作為參數(shù)。 


如果Set()在超時期間未被調(diào)用,則線程被釋放并且WaitOne()返回False。


以下是此功能的實現(xiàn)代碼:


public class OperationHandler

{

    private IOperation _operation;

    private ManualResetEvent _mre = new ManualResetEvent(false);


    public OperationHandler(IOperation operation)

    {

        _operation = operation;

    }

    public void StartWithTimeout(int timeoutMillis)

    {

        _mre.Reset();

        Task.Factory.StartNew(() =>

        {

            bool wasStopped = _mre.WaitOne(timeoutMillis);

            if (!wasStopped)

                _operation.DoOperation();

        });

    }        

    public void StopOperationIfNotStartedYet()

    {

        _mre.Set();

    }

}


執(zhí)行結(jié)果:



我個人非常傾向于這個解決方案,它比我們使用Timer的解決方案更干凈簡潔。

對于我們提出的簡單功能,ManualResetEvent和Timer解決方案都可以正常工作。現(xiàn)在讓我們增加點挑戰(zhàn)性。



新的改進需求


假設(shè)我們現(xiàn)在可以連續(xù)多次調(diào)用StartWithTimeout(),而不是等待第一個超時完成后調(diào)用。


但是這里的預(yù)期行為是什么?實際上存在以下幾種可能性:


  1. 在以前的StartWithTimeout超時期間調(diào)用StartWithTimeout時:忽略第二次啟動。


  2. 在以前的StartWithTimeout超時期間調(diào)用StartWithTimeout時:停止初始話Start并使用新的StartWithTimeout。


  3. 在以前的StartWithTimeout超時期間調(diào)用StartWithTimeout時:在兩個啟動中調(diào)用DoOperation。在StopOperationIfNotStartedYet中停止所有尚未開始的操作(在超時時間內(nèi))。


  4. 在以前的StartWithTimeout超時期間調(diào)用StartWithTimeout時:在兩個啟動中調(diào)用DoOperation。在StopOperationIfNotStartedYet停止一個尚未開始的隨機操作。


可能性1可以通過Timer和ManualResetEvent可以輕松實現(xiàn)。事實上,我們已經(jīng)在我們的Timer解決方案中涉及到了這個。


public void StartWithTimeout(int timeoutMillis)

{

    if (_timer != null)

    return;

    ...

    

    public void StartWithTimeout(int timeoutMillis)

    {

    if (_timer != null)

    return;

    ...

}


可能性2  也可以很容易地實現(xiàn)。


可能性3  不可能通過使用Timer來實現(xiàn)。我們將需要有一個定時器的集合。一旦停止操作,我們需要檢查并處理定時器集合中的所有子項。 


這種方法是可行的,但通過ManualResetEvent我們可以非常簡潔和輕松的實現(xiàn)這一點!


可能性4  跟可能性3相似,可以通過定時器的集合來實現(xiàn)。



可能性3:使用單個ManualResetEvent停止所有操作


讓我們了解一下這里面遇到的難點:


假設(shè)我們調(diào)用StartWithTimeout 10秒超時。


1秒后,我們再次調(diào)用另一個StartWithTimeout,超時時間為10秒。


再過1秒后,我們再次調(diào)用另一個StartWithTimeout,超時時間為10秒。


預(yù)期的行為是這3個操作會依次10秒、11秒和12秒后啟動。


如果5秒后我們會調(diào)用Stop(),那么預(yù)期的行為就是所有正在等待的操作都會停止, 后續(xù)的操作也無法進行。


我稍微改變下Program.cs,以便能夠測試這個操作過程。這是新的代碼:


class Program

{

    static void Main(string[] args)

    {

        var op = new MyOperation();

        var handler = new OperationHandler(op);


        Console.WriteLine("Starting with timeout of 10 seconds, 3 times");

        handler.StartWithTimeout(10 * 1000);

        Thread.Sleep(1000);

        handler.StartWithTimeout(10 * 1000);

        Thread.Sleep(1000);

        handler.StartWithTimeout(10 * 1000);


        Thread.Sleep(13 * 1000);


        Console.WriteLine("Starting with timeout of 10 seconds 3 times, but cancelling after 5 seconds");

        handler.StartWithTimeout(10 * 1000);

        Thread.Sleep(1000);

        handler.StartWithTimeout(10 * 1000);

        Thread.Sleep(1000);

        handler.StartWithTimeout(10 * 1000);


        Thread.Sleep(5 * 1000);

        handler.StopOperationIfNotStartedYet();


        Thread.Sleep(8 * 1000);

        Console.WriteLine("Finished...");

        Console.ReadLine();

    }

}


下面就是使用ManualResetEvent的解決方案:


public class OperationHandler

{

    private IOperation _operation;

    private ManualResetEvent _mre = new ManualResetEvent(false);


    public OperationHandler(IOperation operation)

    {

        _operation = operation;

    }

    public void StartWithTimeout(int timeoutMillis)

    {

        Task.Factory.StartNew(() =>

        {

            bool wasStopped = _mre.WaitOne(timeoutMillis);

            if (!wasStopped)

                _operation.DoOperation();

        });

    }        

    public void StopOperationIfNotStartedYet()

    {

        Task.Factory.StartNew(() =>

        {

            _mre.Set();

            Thread.Sleep(10);//This is necessary because if calling Reset() immediately, not all waiting threads will 'proceed'

            _mre.Reset();

        });

    }

}


輸出結(jié)果跟預(yù)想的一樣:



很開森對不對?


當(dāng)我檢查這段代碼時,我發(fā)現(xiàn)Thread.Sleep(10)是必不可少的,這顯然超出了我的意料。如果沒有它,除3個等待中的線程之外,只有1-2個線程正在進行。很明顯的是,因為Reset()發(fā)生得太快,第三個線程將停留在WaitOne()上。



可能性4:單個AutoResetEvent停止一個隨機操作


假設(shè)我們調(diào)用StartWithTimeout 10秒超時。


1秒后,我們再次調(diào)用另一個StartWithTimeout,超時時間為10秒。


再過1秒后,我們再次調(diào)用另一個StartWithTimeout,超時時間為10秒。然后我們調(diào)用StopOperationIfNotStartedYet()。


目前有3個操作超時,等待啟動。預(yù)期的行為是其中一個被停止, 其他2個操作應(yīng)該能夠正常啟動。


我們的Program.cs可以像以前一樣保持不變。OperationHandler做了一些調(diào)整:


public class OperationHandler

{

    private IOperation _operation;

    private AutoResetEvent _are = new AutoResetEvent(false);


    public OperationHandler(IOperation operation)

    {

        _operation = operation;

    }

    public void StartWithTimeout(int timeoutMillis)

    {

        _are.Reset();

        Task.Factory.StartNew(() =>

        {

            bool wasStopped = _are.WaitOne(timeoutMillis);

            if (!wasStopped)

                _operation.DoOperation();

        });

    }        

    public void StopOperationIfNotStartedYet()

    {

        _are.Set();

    }

}


執(zhí)行結(jié)果是:




結(jié)語


在處理線程通信時,超時后繼續(xù)執(zhí)行某些操作是常見的應(yīng)用。我們嘗試了一些很好的解決方案。一些解決方案可能看起來不錯,甚至可以在特定的流程下工作,但是也有可能在代碼中隱藏著致命的bug。當(dāng)這種情況發(fā)生時,我們應(yīng)對時需要特別小心。


AutoResetEvent和ManualResetEvent是非常強大的類,我在處理線程通信時一直使用它們。這兩個類非常實用。正在跟線程通信打交道的朋友們,快把它們加入到項目里面吧!





- EOF -


該文章在 2023/8/28 15:02:39 編輯過
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點晴ERP是一款針對中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國內(nèi)大量中小企業(yè)的青睞。
點晴PMS碼頭管理系統(tǒng)主要針對港口碼頭集裝箱與散貨日常運作、調(diào)度、堆場、車隊、財務(wù)費用、相關(guān)報表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點,圍繞調(diào)度、堆場作業(yè)而開發(fā)的。集技術(shù)的先進性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點晴WMS倉儲管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質(zhì)期管理,貨位管理,庫位管理,生產(chǎn)管理,WMS管理系統(tǒng),標簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務(wù)都免費,不限功能、不限時間、不限用戶的免費OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved