CODE-非同步方法之同步化及逾時機制

今天遇到的小需求: 有個元件函數以非同步方式執行,透過旗標變數表示執行狀態,我想在Console Application中以同步方式呼叫該函數,等待其執行完畢程式就結束,但要有逾時中止的功能。

非同步函數的例子如以下範例中的AsyncJobClass.DoAsyncWork(),它會以另開執行緒方式執行,在5秒後將Ready旗標設為true。透用這種方式,我模擬了一個非同步執行的作業。若非同步過程發生錯誤時,AsyncJobClass會呼叫自訂OnError事件執行特定邏輯。

我寫了一個AsyncToSyncHelper實現非同步作業的同步化,主要透過方法CheckStatusWithTimeout(Func<bool>, interval, period,  jobDesc),可以在逾時期間(period)內每隔一段時間(interval)檢查Func<bool>一次,直到其傳回true為止。但若等待傳回true的過程超出逾時上限,或非同步作業發生錯誤時,則會抛出例外。跨越不同執行緒的例外處理要額外考量,因此這裡的做法是由非同步作業提供出錯時的自訂事件,在錯誤事件中便可呼叫RaiseCheckStatusException通知CheckStatusWithTimeout停止檢查。

程式碼如下,歡迎參考指教:

using System;
using System.Threading;
 
class Program
{
    static void Main(string[] args)
    {
        AsyncJobClass.Ready = false;
        AsyncJobClass.DoAsyncJob(false);
        //Test 1, Timoeout = 8s
        AsyncToSyncHelper.CheckStatusWithTimeout(
            () => { return AsyncJobClass.Ready; },
            100, 8000, "Test 1");
        Console.WriteLine("Test 1, shorter than timeout, OK!");
 
        try
        {
            AsyncJobClass.Ready = false;
            AsyncJobClass.DoAsyncJob(false);
            //Test 2, Timeout = 3s
            AsyncToSyncHelper.CheckStatusWithTimeout(
                () => { return AsyncJobClass.Ready; },
                100, 3000, "Test 2");
        }
        catch (Exception e)
        {
            Console.WriteLine("Error=" + e.Message);
            Console.WriteLine("Test 2, longer than timeout, OK!");
        }
 
        try
        {
            AsyncJobClass.Ready = false;
            AsyncJobClass.DoAsyncJob(true);
            //when error, use RaiseCheckStatusException() to stop
            AsyncJobClass.OnError = (e) =>
            {
                AsyncToSyncHelper.RaiseCheckStatusException(e);
            };
            //Test 3, throw the exception in purpose
            AsyncToSyncHelper.CheckStatusWithTimeout(
                () => { return AsyncJobClass.Ready; },
                100, 8000, "Test 3");
        }
        catch (Exception e)
        {
            Console.WriteLine("Error=" + e.Message);
            Console.WriteLine("Test 3, stopped when exception, OK!");
        }
 
        Console.Read();
    }
}
 
/// <summary>
/// An helper class to sync with async job
/// </summary>
public class AsyncToSyncHelper
{
    public static Exception CheckStatusException = null;
    /// <summary>
    /// Check the status function until it return true or timeout
    /// </summary>
    /// <param name="check">return true when ready</param>
    /// <param name="interval">interval of checking in milliseconds</param>
    /// <param name="period">timeout period in milliseconds</param>
    /// <param name="jobDesc">job description for timeout exception message</param>
    public static void CheckStatusWithTimeout(Func<bool> check, int interval, 
        long period, string jobDesc)
    {
        DateTime dtTimeout = DateTime.Now.AddMilliseconds(period);
        CheckStatusException = null;
        while (DateTime.Now.CompareTo(dtTimeout) < 0 && !check() 
            && CheckStatusException == null)
            Thread.Sleep(interval);
        if (CheckStatusException != null)
            throw new ApplicationException("CheckStatusWithTimeout Exception: " +
                CheckStatusException.Message);
        if (DateTime.Now.CompareTo(dtTimeout) >= 0)
            throw new ApplicationException(
                string.Format(
                "CheckStatusWithTimeout Exception: Timeout after {0:N0}ms @ {1}",
                period, jobDesc));
    }
 
    private static object _lock = new object();
    /// <summary>
    /// Raise an exception to stop CheckStatusWithTimeout
    /// </summary>
    /// <param name="e"></param>
    public static void RaiseCheckStatusException(Exception e)
    {
        lock (_lock)
        {
            CheckStatusException = e;
        }
    }
 
}
 
class AsyncJobClass
{
    public static bool Ready = false;
    //Simulating async work
    //delay 5 sec, than set Ready = true, or throw an exception in purpose
    public static void DoAsyncJob(bool raiseException) 
    {
        ThreadPool.QueueUserWorkItem((o) => {
            if (raiseException)
                OnError(new ApplicationException("Exception in Purpose!"));
            else
            {
                Thread.Sleep(5000);
                Ready = true;
            }
        });
    }
    //The event triggered when exception raised
    public static Action<Exception> OnError;
}

【補充參考】

歡迎推文分享:
Published 22 March 2010 09:24 PM 由 Jeffrey
Filed under: ,


意見

# chicken said on 22 March, 2010 06:27 AM

提供個好物... .NET FX 裡就有替所有的 delegate 定義非同步呼叫的方法,會用 IAsyncResult 的介面封裝呼叫的結果,其中就提供了 bool IsCompleted {get;} 讓你檢查跑完了沒,也提供 WaitHandle 物件讓你等待它執行完畢 (可以設 timeout, 跟 thread sync 用的是一樣的機制)。

這樣至少是 wait / notify 的模式,不是 pooling 的模式,CPU 會閒很多,同時偵測的精確度也高的多。

# chicken said on 22 March, 2010 09:34 AM

順手補段 sample code, 時間故意都用非整數,主要是看這作法精確度:

   class Program

   {

       private delegate void AsyncMethod();

       static void Main(string[] args)

       {

           AsyncMethod x = DoAsyncJob;

           Stopwatch timer = new Stopwatch();

           timer.Start();

           IAsyncResult result = x.BeginInvoke(null, null);

           while (result.AsyncWaitHandle.WaitOne(543) == false)

           {

               Console.Write('#');

               //Console.WriteLine(DateTime.Now);

           }

           Console.WriteLine("\n{0}", timer.ElapsedMilliseconds);

       }

       private static void DoAsyncJob()

       {

           Thread.Sleep(TimeSpan.FromSeconds(12.345));

       }

   }

# Jeffrey said on 22 March, 2010 04:29 PM

to chicken, 有個小問題,元件是別人提供的,固定只能透過屬性取得處理狀態,在不修改元件的前題下,是否就無法套用IAsyncResult機制,只能走Polling這條路?

# chicken said on 23 March, 2010 06:34 AM

要避開 pooling , 只有兩種方法...

一個是找的到同步呼叫的版本 (就是呼叫後會等到它跑完才RETURN),另一個是要有辦法實作 CALLBACK 的機制 (變數有辦法設 trigger, 或是他們自己提供你指定 callback 等等..) 才有辦法...

否則,就只好自己 pooling 吧 @@

不過自己實作 Async 真的是件苦差事,要是我的話,我大概會做個折衷的版本,就是先用 pooling 重新包裝個很笨的 sync 版本 method, 再用 IAsyncResult 的機制來跑...

你的看法呢?

(必要的) 
(必要的) 
(選擇性的)
(必要的) 

請輸入以上的數字:

搜尋

Go

<March 2010>
SunMonTueWedThuFriSat
28123456
78910111213
14151617181920
21222324252627
28293031123
45678910
 
RSS
【工商服務】

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


BlogLook Score and Rank

Syndication