2016-04-14 18 views
0

私はSOを見て、私の独特の問題に一致するものを見つけることができませんでした。ループ内のEvent.BeginInvoke + Quartz.NET + FileSystemWatcher + Task.Run =例外?

問題: 私はQuartz.NETとFileSystemWatcherを使用していくつかのコードで遊んできましたが、基本的な設定は次のとおりです。

Quartz.NETは15秒間隔でタイマーを起動します。このトリガーにより、ファイル名の形式が* .csv(または* .xml - 例のみ)の特定のディレクトリにファイルが作成されます。 FileSystemWatcherはファイルに気づき、同様のディレクトリに* .csvを吐き出すコードを実行します。今私はこれがウォッチャーを再トリガすることを知っており、それは無限のループで立ち往生するでしょう - 私はかつてこれを行うことを意味しています。

このコードが実行されると、イベントを管理するハンドラコードで範囲外例外が発生します。この例外は、Quartzジョブを処理するときとforループを使用するときに発生していることに注意してください。 foreachでは例外が発生しません

私はこのコードが...面白いかもしれないことを認識していますが、誰もがfor(余分な範囲外の例外)の余分なインクリメントを引き起こし、イテ​​レータが成功する理由を解読できますか?

アップデート2016年4月14日8:05 EST

ジョンは、それが簡単な監督と睡眠不足だっ指摘したように。タスクが実行されるときiは共有リソースです。

   var tl = new Task[pdList.Count]; 
       for (int i = 0; i < pdList.Count; i++) 
       { 
        int li = i; // i is shared...use local copy 
           // use foreach instead 
        tl[i] = Task.Factory.StartNew(() => { ExecuteProcess(pdList[li], hargs); }, TaskCreationOptions.LongRunning); 
       } 

アップデート2016年4月14日7:09 EST私はうまくいけば、問題を再現するコードの小さな作品を書きます

パージョンの指導。その間、私はスタックトレースと例外の詳細を追加しました。プロジェクト全体はでgithubの上で自由に利用可能です。

https://github.com/banjoCoder/ThatIntegrationEngine/tree/Dev

例外情報

インデックスが範囲外でした。負でなく、コレクションのサイズが 未満である必要があります。パラメータ名:インデックス

例外スタックトレース:

at System.Environment.GetStackTrace(Exception e, Boolean needFileInfo) 
    at System.Environment.get_StackTrace() 
    at ThatIntegrationEngine.Engine.<>c__DisplayClass25_1.<BeginProcessExecution>b__0() in Z:\src\ThatIntegrationEngine\ThatIntegrationEngine.Core\Hood\Engine.cs:line 263 
    at System.Threading.Tasks.Task.InnerInvoke() 
    at System.Threading.Tasks.Task.Execute() 
    at System.Threading.Tasks.Task.ExecutionContextCallback(Object obj) 
    at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) 
    at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) 
    at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot) 
    at System.Threading.Tasks.Task.ExecuteEntry(Boolean bPreventDoubleExecution) 
    at System.Threading.Tasks.ThreadPoolTaskScheduler.LongRunningThreadWork(Object obj) 
    at System.Threading.ThreadHelper.ThreadStart_Context(Object state) 
    at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) 
    at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) 
    at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) 
    at System.Threading.ThreadHelper.ThreadStart(Object obj) 
    at System.Environment.GetStackTrace(Exception e, Boolean needFileInfo) 
    at System.Environment.get_StackTrace() 
    at ThatIntegrationEngine.Engine.<>c__DisplayClass25_1.<BeginProcessExecution>b__0() in Z:\src\ThatIntegrationEngine\ThatIntegrationEngine.Core\Hood\Engine.cs:line 263 
    at System.Threading.Tasks.Task.InnerInvoke() 
    at System.Threading.Tasks.Task.Execute() 
    at System.Threading.Tasks.Task.ExecutionContextCallback(Object obj) 
    at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) 
    at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) 
    at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot) 
    at System.Threading.Tasks.Task.ExecuteEntry(Boolean bPreventDoubleExecution) 
    at System.Threading.Tasks.ThreadPoolTaskScheduler.LongRunningThreadWork(Object obj) 
    at System.Threading.ThreadHelper.ThreadStart_Context(Object state) 
    at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) 
    at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) 
    at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) 
    at System.Threading.ThreadHelper.ThreadStart(Object obj) 
Exception thrown: 'System.ArgumentOutOfRangeException' in mscorlib.dll 

コードの流れ:

イベントが発生クォーツトリガ火災:

タスクスレッド内の

at System.ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource) 
    at System.Collections.Generic.List`1.get_Item(Int32 index) 
    at ThatIntegrationEngine.Engine.<>c__DisplayClass25_1.<BeginProcessExecution>b__0() in Z:\src\ThatIntegrationEngine\ThatIntegrationEngine.Core\Hood\Engine.cs:line 265 
    at System.Threading.Tasks.Task.InnerInvoke() 
    at System.Threading.Tasks.Task.Execute() 

スタックトレースコール

protected virtual void OnTimedActionAsync(object s, CronSchedulerEventArgs e) 
{ 
    EventHandler<CronSchedulerEventArgs> handler = TimedActionAsync; 
    handler?.BeginInvoke(this, e, cs => 
    { 
     var delg = (EventHandler<CronSchedulerEventArgs>)cs.AsyncState; 
     delg.EndInvoke(cs); 
    }, 
    handler); 
} 
FSWに加入した内部クラスによって提起

非同期イベントがイベント

protected virtual void OnCreatedAsync(object sender, FileSystemEventArgs args) 
{ 
    EventHandler<DirWatcherEventArgs> handler = FileArrivedAsync; 
    handler?.BeginInvoke(this, new DirWatcherEventArgs(args.FullPath, WatcherChangeTypes.Created, Id, Name) 
    , cs => 
     { 
      var delg = (EventHandler<DirWatcherEventArgs>)cs.AsyncState; 
      delg.EndInvoke(cs); 
     } 
    , handler); 
} 

を作成したジョンスキートはそれが簡単な監督と睡眠不足だっ指摘したように最後のイベントは

private void HandleScheduledActionAsync(object sender, CronSchedulerEventArgs csarg) 
    { 
     var scheduler = sender as ICronScheduler; 
     if (scheduler != null) 
     { 
      // load process type based on trigger (sender) id 
      // dynamically create instance of the process and pass 
      // all expected information 
      BeginProcessExecution(csarg); 
     } 
    } 

    private void HandleFileArrivalAsync(object sender, DirWatcherEventArgs dwargs) 
    { 
     var watcher = sender as IDirectoryWatcher; 
     if (watcher != null) 
     { 
      // load process type based on trigger (sender) id 
      // dynamically create instance of the process and pass 
      // all expected information 
      BeginProcessExecution(dwargs); 
     } 
    } 

    private void BeginProcessExecution(HandlerEventArgs hargs) 
    { 
     IList<IProcessDetails> pdList = null; 

     if (this._processHandlerRelation.TryGetValue(hargs.TriggerId, out pdList)) 
     { 
      if (pdList.Count > 1) 
      { 
       try 
       { 
        //!!! FAILURE !!! 
        //this fails with out of range exception 
        var tl = new Task[pdList.Count]; 
        for(int i = 0; i < pdList.Count; i++) 
        { 
         //!!! EXCEPTION !!! 
         // this causes an out of range exception 
         // at exec task is attempting to read final value of i       
         // is i accessed by ref? -- no it's a shared resource 
         tl[i] = Task.Factory.StartNew(() => ExecuteProcess(pdList[i], hargs), TaskCreationOptions.LongRunning); 
        } 

        //this does not cause an exception 
        //var tl = new List<Task>(); 
        //foreach (var pd in pdList) 
        //{ 
        // tl.Add(Task.Factory.StartNew(() => ExecuteProcess(pd, hargs), TaskCreationOptions.LongRunning)); 
        //} 

        Task.WaitAll(tl); 

        // this does not cause an exception 
        //Parallel.ForEach(pdList, pd => ExecuteProcess(pd, hargs));      
       } 
       catch (Exception e) 
       { 
        // log exception 
       } 
      } 
      else 
      {// avoid parallelization if only one process is expected 
      // to be executed 
       // execute on calling thread 
       ExecuteProcess(pdList[0], hargs); 
      } 
     } 
    } 
+0

正確な例外とどことは何ですか?これを[mcve]で再現できますか? (ここにはたくさんのコードがあります...問題を失うことなくそれを減らすことができれば幸いです。) –

+0

@JonSkeetもっと小さな複製可能なコードを手に入れようと思います。 –

+0

@JonSkeet私はforループの中にローカルintを代入し、それをタスクに渡すことによって例外を回避することができます。それは、実行時にタスクが参照で私をつかんでいるように見えますが、ループが起こっている時間は境界の外にあるNにあります。私はコードが動作するノートを追加しました –

答えて

0

を扱っています。タスクが実行されるとき、iは共有リソースです。

  var tl = new Task[pdList.Count]; 
      for (int i = 0; i < pdList.Count; i++) 
      { 
       int li = i; // i is shared...use local copy 
          // use foreach instead 
       tl[i] = Task.Factory.StartNew(() => ExecuteProcess(pdList[li], hargs), TaskCreationOptions.LongRunning); 
      } 

作業溶液:

    int pi = 0;  
        var plist = new Task[pdList.Count];       
        foreach(var pd in pdList) 
        { 
         plist[pi++] = Task.Factory.StartNew(() => ExecuteProcess(pd, hargs), TaskCreationOptions.LongRunning); 
        } 

        Task.WaitAll(plist);