2017-12-20 18 views
1

大きなテキストファイルをTextBoxに読み込み、ファイルがテキストボックスにドラッグされたときに応答するようにしています。Windowsフォームで長いタスクを実行しているときにUIスレッドを応答させ続ける

期待どおりに動作しません。ウィンドウのフォームがフリーズし、ファイルを読み込んでテキストボックスに内容を追加する作業が行われているように見えます。

IDEによってContextSwitchDeadLockがスローされましたが、実際にはエラーではありません。 これは長時間実行されるタスクです。例外メニューの下で動作を変更するように修正しました。

JStewardのおかげで、Peterはコードをこれに変更しました。

このタスクを実行しているときに私はどのようにしてui(メインスレッド)を応答し続けることができますか?おかげさまで

private SynchronizationContext fcontext; 

public Form1() 
{  
    InitializeComponent();    
    values.DragDrop += values_DragDrop; //<----------- This is a textbox 
    fcontext = WindowsFormsSynchronizationContext.Current; 
} 

// The async callback 
async void values_DragDrop(object sender, DragEventArgs e) 
{ 
    try 
    { 
     string dropped = ((string[]) e.Data.GetData(DataFormats.FileDrop))[0]; 
     if (dropped.Contains(".csv") || dropped.Contains(".txt")) 
     { 
       using (StreamReader sr = File.OpenText(dropped)) 
       { 
        string s = String.Empty; 
        while ((s = await sr.ReadLineAsync()) != null) 
        {                 
         values.AppendText(s.Replace(";","")); 
        } 
       }     
     } 
    } 
    catch (Exception ex) { } 
} 
+2

'File' APIのブロックバリアントを使うのではなく、' AppendText(待ち受け... ReadLineAsync) 'を使うことができます。そうすれば、手動でコンテキストを保存したりポストする必要がなくなります。 – JSteward

+0

@JSteward試した値.AppendText(await sr.ReadLineAsync())が待ち受けているのは、lamba式にのみ適用できます。 – ppk

+2

** 'async' ** lamda式にしか適用できないと思います。質問のコードを更新できますか? – JSteward

答えて

2

時には実際にUIスレッド(例えば、構文強調表示、スペルチェック-AS-あなた型など)の上にいくつかの非同期、バックグラウンド操作を行うために必要とされます。私はあなたの特定の(IMO、考案された)例題のデザイン問題に疑問を呈しません - ここではMVVMパターンを使用するべきでしょうが、UIスレッドを確実に応答させることができます。

保留中のユーザー入力を検出し、メインメッセージループに返すことで、処理の優先度を設定できます。ここでは、あなたが解決しようとしている課題に基づいて、WinFormsでそれを行う方法の完全なカットアンドペーストアンドランの例があります。

using System; 
using System.Runtime.InteropServices; 
using System.Threading; 
using System.Threading.Tasks; 
using System.Windows.Forms; 

namespace WinFormsYield 
{ 
    static class Program 
    { 
     // a long-running operation on the UI thread 
     private static async Task LongRunningTaskAsync(Action<string> deliverText, CancellationToken token) 
     { 
      for (int i = 0; i < 10000; i++) 
      { 
       token.ThrowIfCancellationRequested(); 
       await InputYield(token); 
       deliverText(await ReadLineAsync(token)); 
      } 
     } 

     [STAThread] 
     static void Main() 
     { 
      Application.EnableVisualStyles(); 
      Application.SetCompatibleTextRenderingDefault(false); 

      // create some UI 

      var form = new Form { Text = "Test", Width = 800, Height = 600 }; 

      var panel = new FlowLayoutPanel 
      { 
       Dock = DockStyle.Fill, 
       FlowDirection = FlowDirection.TopDown, 
       WrapContents = true 
      }; 

      form.Controls.Add(panel); 
      var button = new Button { Text = "Start", AutoSize = true }; 
      panel.Controls.Add(button); 

      var inputBox = new TextBox 
      { 
       Text = "You still can type here while we're loading the file", 
       Width = 640 
      }; 
      panel.Controls.Add(inputBox); 

      var textBox = new TextBox 
      { 
       Width = 640, 
       Height = 480, 
       Multiline = true, 
       ReadOnly = false, 
       AcceptsReturn = true, 
       ScrollBars = ScrollBars.Vertical 
      }; 
      panel.Controls.Add(textBox); 

      // handle Button click to "load" some text 

      button.Click += async delegate 
      { 
       button.Enabled = false; 
       textBox.Enabled = false; 
       inputBox.Focus(); 
       try 
       { 
        await LongRunningTaskAsync(text => 
         textBox.AppendText(text + Environment.NewLine), 
         CancellationToken.None); 
       } 
       catch (Exception ex) 
       { 
        MessageBox.Show(ex.Message); 
       } 
       finally 
       { 
        button.Enabled = true; 
        textBox.Enabled = true; 
       } 
      }; 

      Application.Run(form); 
     } 

     // simulate TextReader.ReadLineAsync 
     private static async Task<string> ReadLineAsync(CancellationToken token) 
     { 
      return await Task.Run(() => 
      { 
       Thread.Sleep(10); // simulate some CPU-bound work 
       return "Line " + Environment.TickCount; 
      }, token); 
     } 

     // 
     // helpers 
     // 

     private static async Task TimerYield(int delay, CancellationToken token) 
     { 
      // yield to the message loop via a low-priority WM_TIMER message (used by System.Windows.Forms.Timer) 
      // https://web.archive.org/web/20130627005845/http://support.microsoft.com/kb/96006 

      var tcs = new TaskCompletionSource<bool>(); 
      using (var timer = new System.Windows.Forms.Timer()) 
      using (token.Register(() => tcs.TrySetCanceled(), useSynchronizationContext: false)) 
      { 
       timer.Interval = delay; 
       timer.Tick += (s, e) => tcs.TrySetResult(true); 
       timer.Enabled = true; 
       await tcs.Task; 
       timer.Enabled = false; 
      } 
     } 

     private static async Task InputYield(CancellationToken token) 
     { 
      while (AnyInputMessage()) 
      { 
       await TimerYield((int)NativeMethods.USER_TIMER_MINIMUM, token); 
      } 
     } 

     private static bool AnyInputMessage() 
     { 
      var status = NativeMethods.GetQueueStatus(NativeMethods.QS_INPUT | NativeMethods.QS_POSTMESSAGE); 
      // the high-order word of the return value indicates the types of messages currently in the queue. 
      return status >> 16 != 0; 
     } 

     private static class NativeMethods 
     { 
      public const uint USER_TIMER_MINIMUM = 0x0000000A; 
      public const uint QS_KEY = 0x0001; 
      public const uint QS_MOUSEMOVE = 0x0002; 
      public const uint QS_MOUSEBUTTON = 0x0004; 
      public const uint QS_POSTMESSAGE = 0x0008; 
      public const uint QS_TIMER = 0x0010; 
      public const uint QS_PAINT = 0x0020; 
      public const uint QS_SENDMESSAGE = 0x0040; 
      public const uint QS_HOTKEY = 0x0080; 
      public const uint QS_ALLPOSTMESSAGE = 0x0100; 
      public const uint QS_RAWINPUT = 0x0400; 

      public const uint QS_MOUSE = (QS_MOUSEMOVE | QS_MOUSEBUTTON); 
      public const uint QS_INPUT = (QS_MOUSE | QS_KEY | QS_RAWINPUT); 

      [DllImport("user32.dll")] 
      public static extern uint GetQueueStatus(uint flags); 
     } 
    } 
} 

今、あなたはそれがまだバックグラウンド上のテキストが移入されている間、ユーザは、エディタの内容を変更する場合はやろうとしている何を自問する必要がありますだけではないawait InputYield(token)に注意してください。ここでは簡単にするために、ボタンとエディタ自体を無効にしています(残りのUIはアクセス可能で反応性があります)が、質問は開いたままです。また、このサンプルの範囲外にある取り消しロジックの実装を検討する必要があります。

+0

ニース!しかし、私は質問があります。なぜあなたはThread.Dleayの代わりにThread.Sleepを使用するのですか? – ppk

+0

@ppkの 'ReadLineAsync'実装はテスト用のモックアップです。 'Thread.Sleep'を' Task.Run'ラムダの内部で使用しています。これは、CPUスレッドがテキストを生成してプールスレッドに渡す作業を意味します。テストのためのもの以外には、実際には実のコードでは 'Thread.Sleep'はほとんど必要ありません。 – Noseratio

+2

追加の説明をありがとう。 – ppk

1

おそらくMicrosoftのリアクティブフレームワークを使用してください。ここでは、必要なコードは次のとおりです。

.SelectMany(dropped => { values.Text = ""; return System.IO.File.ReadLines(dropped); }) 

NuGet「System.Reactive」&「System.Reactive:

using System.Reactive.Concurrency; 
using System.Reactive.Linq; 

namespace YourNamespace 
{ 
    public partial class Form1 : Form 
    { 
     public Form1() 
     { 
      InitializeComponent(); 

      IDisposable subscription = 
       Observable 
        .FromEventPattern<DragEventHandler, DragEventArgs>(h => values.DragDrop += h, h => values.DragDrop -= h) 
        .Select(ep => ((string[])ep.EventArgs.Data.GetData(DataFormats.FileDrop))[0]) 
        .ObserveOn(Scheduler.Default) 
        .Where(dropped => dropped.Contains(".csv") || dropped.Contains(".txt")) 
        .SelectMany(dropped => System.IO.File.ReadLines(dropped)) 
        .ObserveOn(this) 
        .Subscribe(line => values.AppendText(line + Environment.NewLine)); 
     } 
    } 
} 

あなたは、これで.SelectManyを置き換える値を追加する前に、テキストボックスをオフにしたいはずです。 Windows.Forms "を使用してビットを取得します。

フォームを閉じるときには、subscription.Dispose()を実行してイベントハンドラを削除してください。

2

UIを応答し続ける必要がある場合は、息をする時間を与えてください。
1行のテキストを読むのは非常に速いので、(ほとんど)何も待たずに、UIの更新に時間がかかります。少しでも遅延を挿入すると、UIが更新されます。 Task.Factory
TPLを使用して(のSynchronizationContextをのawaitにより捕捉される)

public Form1() 
{ 
    InitializeComponent(); 
    values.DragDrop += new DragEventHandler(this.OnDrop); 
    values.DragEnter += new DragEventHandler(this.OnDragEnter); 
} 

public async void OnDrop(object sender, DragEventArgs e) 
{ 
    string _dropped = ((string[])e.Data.GetData(DataFormats.FileDrop))[0]; 
    if (_dropped.Contains(".csv") || _dropped.Contains(".txt")) 
    { 
     try 
     { 
     string _s = string.Empty; 
     using (TextReader tr = new StreamReader(_dropped)) 
     { 
      while (tr.Peek() >= 0) 
      { 
       _s = await tr.ReadLineAsync(); 
       values.AppendText(_s.Replace(";", " ") + "\r\n"); 
       await Task.Delay(10); 
      } 
     } 
     } 
     catch (Exception) { 
     //Do something here 
     } 
    } 
} 

private void OnDragEnter(object sender, DragEventArgs e) 
{ 
    e.Effect = e.Data.GetDataPresent(DataFormats.FileDrop, false) ? 
            DragDropEffects.Copy : 
            DragDropEffects.None; 
} 

TPL非同期/待つを使用

はTaskSchedulerを通じてタスクを実行します。
TaskSchedulerを使用して、タスクをSynchronizationContextにキューイングすることができます。

TaskScheduler _Scheduler = TaskScheduler.FromCurrentSynchronizationContext(); 

//No async here 
public void OnDrop(object sender, DragEventArgs e) 
{ 
    string _dropped = ((string[])e.Data.GetData(DataFormats.FileDrop))[0]; 
    if (_dropped.Contains(".csv") || _dropped.Contains(".txt")) 
    { 
     Task.Factory.StartNew(() => 
     { 
     string _s = string.Empty; 
     int x = 0; 
     try 
     { 
      using (TextReader tr = new StreamReader(_dropped)) 
      { 
       while (tr.Peek() >= 0) 
       { 
        _s += (tr.ReadLine().Replace(";", " ")) + "\r\n"; 
        ++x; 
        //Update the UI after reading 20 lines 
        if (x >= 20) 
        { 
        //Update the UI or report progress 
        Task UpdateUI = Task.Factory.StartNew(() => 
        { 
         try { 
          values.AppendText(_s); 
         } 
         catch (Exception) { 
          //An exception is raised if the form is closed 
         } 
        }, CancellationToken.None, TaskCreationOptions.PreferFairness, _Scheduler); 
        UpdateUI.Wait(); 
        x = 0; 
        } 
       } 
      } 
     } 
     catch (Exception) { 
      //Do something here 
     } 
     }); 
    } 
} 
関連する問題