簡體   English   中英

實時迭代應用程序的體系結構

[英]Architecture of real-time, iteration application

抱歉抽象的問題,但是我正在尋找一些有關應用程序類型的示例/建議/文章,這些示例/建議/文章會在周期中執行一些等效操作,並且周期的每次迭代都應在一定的時間段(例如10秒)內顯示其結果。 。

我的應用程序在外部WCF服務和本地數據庫之間進行數據同步。 在每次迭代中,應用程序都會檢索對WCF服務的數據傳遞請求的更改,並將更改放入數據庫,反之亦然。 此應用程序最困難的要求之一是,迭代應每十秒鍾觸發一次。

因此,這里出現了問題。 我如何保證迭代完成不超過10秒?

我猜這種類型的應用程序稱為實時應用程序(以實時操作系統的形式)。

我們使用的DAL組件隨機作用於連接超時行為。 因此,數據庫操作可能需要10秒以上的時間。

這是一次迭代的估計代碼:

        Stopwatch s1 = new Stopwatch();
        s1.Start();
        Parallel.ForEach(Global.config.databases, new ParallelOptions { MaxDegreeOfParallelism = -1 }, (l) =>            
        {
            Console.WriteLine("Started for {0}", l.key.name);                
            DB db = new DB(l.connectionString);

            DateTime lastIterationTS = GetPreviousIterationTS(l.id);

            ExternalService serv = new ExternalService(l.id);
            List<ChangedData> ChangedDataDb = db.GetChangedData(DateTime.Now.AddSeconds((lastIterationTS == DateTime.MinValue) ? -300 : -1 * (DateTime.Now - lastIterationTS).Seconds));

            List<Data> ChangedDataService = serv.GetModifiedData();                

                    Action syncDBChanges = new Action(() =>
                        {
                            // Изменения в БД                                   
                            foreach (ChangedData d in ChangedDataDb)
                            {
                                try
                                {
                                    // ...
                                    // analyzing & syncing
                                }
                                catch (Exception e)
                                {
                                    logger.InfoEx("Exception_SyncDatabase", e.ToString());
                                }
                            }
                        }
                    );

                    Action syncService = new Action(() =>
                    {                            
                        foreach (Data d in ChangedDataService)
                        {
                            try
                            {
                                // ...
                                // analyzing & syncing
                            }
                            catch (Exception e)
                            {
                                logger.InfoEx("Exception_SyncService", e.ToString());
                            }
                        }
                    });

                    List<WaitHandle> handles = new List<WaitHandle>();
                    IAsyncResult ar1 = syncDBChanges.BeginInvoke(syncDBChanges.EndInvoke, null);
                    IAsyncResult ar2 = syncService.BeginInvoke(syncService.EndInvoke, null);

                    handles.Add(ar1.AsyncWaitHandle);
                    handles.Add(ar2.AsyncWaitHandle);

                    WaitHandle.WaitAll(handles.ToArray(), (int)((Global.config.syncModifiedInterval - 1) * 1000));
                    SetCurrentIterationTS(l.id);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                    logger.InfoEx("Exception_Iteration", e.ToString());
                    continue;
                }
            }
            logger.InfoEx("end_Iteration", IterationContextParams);
        }
        );
        s1.Stop();
        Console.WriteLine("Main iteration done for {0}...", s1.Elapsed);        

您可以考慮幾個選擇...

  1. 如果迭代超過10秒,則終止該迭代,並希望下一次迭代可以完成該過程。 這種方法的問題在於,很可能所有迭代都不會完成,因此同步過程將永遠不會發生。 我建議以下選項...

  2. 如果迭代花費的時間超過10秒,請等待其完成並跳過下一個迭代。 這樣,您可以確保過程至少完成一次。 以下是簡化的代碼示例,以供參考...

     class Updater { Timer timer = new Timer(); public object StateLock = new object(); public string State; public Updater() { timer.Elapsed += timer_Elapsed; timer.Interval = 10000; timer.AutoReset = true; timer.Start(); } void timer_Elapsed(object sender, ElapsedEventArgs e) { if (State != "Running") { Process(); } } private void Process() { try { lock (StateLock) { State = "Running"; } // Process lock (StateLock) { State = ""; } } catch { throw; } } } 

...

class Program
{
    static void Main(string[] args)
    {
        Updater updater = new Updater();
        Console.ReadLine();
    }
}

Quartz.net是.NET平台的出色調度程序,我認為它可以滿足您的需求。

  • 如果要取消工作,可以實現IInterruptableJob 您應該能夠在Interupt方法中添加一些清理代碼來處理所有數據庫連接。
  • 如果您想完成一項工作,但僅在最后一項完成后才開始另一項工作(我認為這是更好的選擇),則可以實現IStatefulJob接口

我通常將更新周期與實際計時器分開

計時器執行兩件事:

1)如果更新未運行則啟動它。

2)如果服務已經在運行,請設置一個標志以使其繼續運行。

更新周期:

1)設置運行標志

2)做更新

3)將運行標志設置為false

4)如果將繼續運行設置為1)。

您可能想閱讀有關.Net中可用的各種Timer對象的信息: http : //msdn.microsoft.com/zh-cn/magazine/cc164015.aspx

我個人喜歡System.Threading.Timer因為您可以輕松使用lambda,並且如果您創建單獨的回調,它可以傳遞狀態對象。

我還建議您使用System.Threading.Tasks庫,因為它允許您在工作完成之前經過計時器的情況下,優雅地處理取消操作。 MSDN示例: http : //msdn.microsoft.com/en-us/library/dd537607.aspx

這是一個在10分鍾的計時器中同時使用這些組件的示例:注意:要對sql數據庫執行此操作,您需要將Asynchronous Processing=true;設置Asynchronous Processing=true; MultipleActiveResultSets=True;

CancellationTokenSource cancelSource = new CancellationTokenSource();
System.Threading.Timer timer = new System.Threading.Timer(callback =>
{
    //start sync
    Task syncTask = Task.Factory.StartNew(syncAction =>
        {
            using (SqlConnection conn = 
                new SqlConnection(
                   ConfigurationManager.ConnectionStrings["db"].ConnectionString))
            {
                conn.Open();
                using (SqlCommand syncCommand = new SqlCommand
                {
                    CommandText = "SELECT getdate() \n WAITFOR DELAY '00:11'; ",
                    CommandTimeout = 600,
                    Transaction = conn.BeginTransaction(),
                    Connection = conn
                })
                {
                    try
                    {
                        IAsyncResult t = syncCommand.BeginExecuteNonQuery();
                        SpinWait.SpinUntil(() => 
                            (t.IsCompleted || cancelSource.Token.IsCancellationRequested));
                        if (cancelSource.Token.IsCancellationRequested && !t.IsCompleted)
                            syncCommand.Transaction.Rollback();

                    }
                    catch (TimeoutException timeoutException)
                    {
                        syncCommand.Transaction.Rollback();
                        //log a failed sync attepmt here
                        Console.WriteLine(timeoutException.ToString());
                    }
                    finally
                    {
                        syncCommand.Connection.Close();
                    }
                }
            }
        }, null, cancelSource.Token);
    //set up a timer for processing in the interim, save some time for rollback
    System.Threading.Timer spinTimer = new System.Threading.Timer(c => {
        cancelSource.Cancel();
    }, null, TimeSpan.FromMinutes(9), TimeSpan.FromSeconds(5)); 

    //spin here until the spintimer elapses;
    //this is optional, but would be useful for debugging.
    SpinWait.SpinUntil(()=>(syncTask.IsCompleted || cancelSource.Token.IsCancellationRequested));
    if (syncTask.IsCompleted || cancelSource.Token.IsCancellationRequested)
        spinTimer.Dispose();

}, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(10));

也許嘗試一下。 請確保您沒有在DoWork()方法中創建和使用任何新線程。

class DatabaseUpdater
{
    private readonly Timer _timer;
    private List<Thread> _threads;
    private readonly List<DatabaseConfig> _dbConfigs;

    public DatabaseUpdater(int seconds, List<DatabaseConfig> dbConfigs)
    {
        _timer = new Timer(seconds * 1000);
        _timer.Elapsed += TimerElapsed;
        _dbConfigs = dbConfigs;
    }

    public void Start()
    {
        StartThreads();
        _timer.Start();
    }

    public void Stop()
    {
        _timer.Stop();
        StopThreads();
    }

    void TimerElapsed(object sender, ElapsedEventArgs e)
    {
        StopThreads();
        StartThreads();
    }

    private void StartThreads()
    {
        var newThreads = new List<Thread>();
        foreach (var config in _dbConfigs)
        {
            var thread = new Thread(DoWork);
            thread.Start(config);
            newThreads.Add(thread);
        }

        _threads = newThreads;
    }

    private void StopThreads()
    {
        if (_threads == null) return;

        var oldThreads = _threads;
        foreach (var thread in oldThreads)
        {
            thread.Abort();
        }
    }

    static void DoWork(object objConfig)
    {
        var dbConfig = objConfig as DatabaseConfig;
        if (null == dbConfig) return;

        var n = GetRandomNumber();

        try
        {
            ConsoleWriteLine("Sync started for : {0} - {1} sec work.", dbConfig.Id, n);

            // update/sync db
            Thread.Sleep(1000 * n);

            ConsoleWriteLine("Sync finished for : {0} - {1} sec work.", dbConfig.Id, n);
        }
        catch (Exception ex)
        {
            // cancel/rollback db transaction
            ConsoleWriteLine("Sync cancelled for : {0} - {1} sec work.",
                dbConfig.Id, n);
        }
    }

    static readonly Random Random = new Random();

    [MethodImpl(MethodImplOptions.Synchronized)]
    static int GetRandomNumber()
    {
        return Random.Next(5, 20);
    }

    [MethodImpl(MethodImplOptions.Synchronized)]
    static void ConsoleWriteLine(string format, params object[] arg)
    {
        Console.WriteLine(format, arg);
    }
}

static void Main(string[] args)
{
    var configs = new List<DatabaseConfig>();
    for (var i = 1; i <= 3; i++)
    {
        configs.Add(new DatabaseConfig() { Id = i });
    }

    var databaseUpdater = new DatabaseUpdater(10, configs);
    databaseUpdater.Start();

    Console.ReadKey();

    databaseUpdater.Stop();
}

暫無
暫無

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

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