1

抽象的な質問で申し訳ありませんが、サイクルで同等の操作を行うアプリケーションの種類に関するサンプル/アドバイス/記事を探しています。サイクルのすべての反復は、特定の時間 (たとえば、10 秒) で結果を公開する必要があります。 .

私のアプリケーションは、外部 WCF サービスとローカル データベースの間でデータの同期を行います。すべての反復で、アプリケーションは WCF サービスに要求を渡すデータの変更を取得し、変更をデータベースに配置します。その逆も同様です。このアプリケーションの最も厳しい要件の 1 つは、反復を 10 秒ごとに実行することです。

ここで問題が発生します。反復が 10 秒以内に終了することを保証するにはどうすればよいですか?

このタイプのアプリケーションは、リアルタイム アプリケーション (リアルタイム OS の意味で) と呼ばれていると思います。

私たちが使用する DAL コンポーネントは、接続タイムアウトの動作にランダムに作用します。そのため、DB 操作には 10 秒以上かかる場合があります。

1回の反復の推定コードは次のとおりです。

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

5 に答える 5

2

いくつかのオプションを検討できます...

  1. 10 秒を超えた場合は反復を終了し、次の反復でプロセスが完了することを期待します。このアプローチの問題点は、どの反復も完了しない可能性が高く、したがって同期プロセスが発生しないことです。次のオプションをお勧めします...

  2. 反復に 10 秒以上かかる場合は、完了するまで待って、次の反復をスキップします。このようにして、プロセスが少なくとも 1 回は完了するようにします。以下は、参照用の簡略化されたコード サンプルです...

    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();
    }
}
于 2013-09-19T18:21:09.970 に答える
1

Quartz.netは、.NET プラットフォーム用の優れたスケジューラーであり、あなたのニーズに合うと思います。

  • ジョブを強制終了したい場合は、IInterruptableJobを実装できます。Interupt メソッドにいくつかのクリーンアップ コードを追加して、db 接続を破棄できるはずです。
  • ジョブを終了したいが、最後のジョブが完了した場合にのみ別のジョブを開始する場合 (これがより良いオプションだと思います)、IStatefulJobインターフェイスを実装できます。
于 2013-09-22T22:14:04.243 に答える
0

私は通常、更新サイクルを実際のタイマーから切り離します

タイマーは次の 2 つのことを行います。

1) 更新が実行されていない場合は開始します。

2) サービスが既に実行されている場合は、実行を継続するためのフラグを設定します。

更新サイクル:

1) 実行フラグを設定する

2) アップデートを行う

3) 実行フラグを false に設定する

4) 継続実行が設定されている場合は、1) に進みます。

于 2013-09-18T11:35:28.170 に答える
0

おそらくこれを試してみてください。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();
}
于 2013-09-23T01:59:26.950 に答える
0

.Net で利用可能なさまざまな Timer オブジェクトについて読みたいと思うかもしれません: http://msdn.microsoft.com/en-us/magazine/cc164015.aspx

System.Threading.Timerラムダを簡単に使用でき、別のコールバックを作成すると状態オブジェクトを渡すことができるため、個人的に気に入っています。

System.Threading.Tasksまた、ライブラリを使用すると、作業が完了する前にタイマーが経過した場合にキャンセルを適切に処理できるため、ライブラリを使用することをお勧めします。MSDN の例: http://msdn.microsoft.com/en-us/library/dd537607.aspx

これらのコンポーネントを 10 分のタイマーで一緒に使用する例を次に示します。 注: SQL データベースでこれを行うには、設定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));
于 2013-09-22T22:23:01.207 に答える