2017-12-20 2 views
1

큰 텍스트 파일을 TextBox로 읽어 들이고 파일을 텍스트 상자로 끌면 응답을 유지하려고합니다.Windows에서 긴 작업을 실행하는 경우 UI 스레드가 응답하도록 유지

예상대로 작동하지 않으며 Windows 양식이 고정되어 있으며 파일을 읽고 텍스트 상자에 내용을 추가하는 작업 만 수행됩니다.

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

당신 수 단지'...에서는 AppendText (await를 ... ReadLineAsync)'대신'File' API의 차단 변형을 사용. 그렇게하면 수동으로 컨텍스트에 저장하거나 게시 할 필요가 없습니다. – JSteward

+0

@JSteward 시도한 값 .AppendText (sr.ReadLineAsync()를 기다림)하지만 lamba 표현식에만 적용될 수 있습니다. – ppk

+2

**'async' ** lamda 표현식에만 적용 할 수 있다는 것을 의미한다고 생각합니다. 질문에서 코드를 업데이트 할 수 있습니까? – JSteward

답변

2

은 때때로 참으로 UI 스레드 (예를 들어, 구문 강조, 맞춤법 검사로서의 당신 형 등)에 대한 몇 가지 비동기, 백그라운드 작업을 수행하는 데 필요합니다. 특정 (IMO, 고안된) 예제의 디자인 문제에 대해서는 질문하지 않을 것입니다. MVV 패턴을 사용해야 할 가능성이 높습니다. 그러나 UI 스레드를 응답 성있게 유지할 수는 있습니다.

보류중인 사용자 입력을 감지하고 주 메시지 루프에 응답하여 처리 우선 순위를 부여함으로써이를 수행 할 수 있습니다. 여기 당신이 해결하려고하는 작업을 기반으로 WinForms에서이를 수행하는 방법에 대한 완전하고 잘린 - 붙여 넣기 - 실행 예제가 있습니다.그냥 않는 await InputYield(token) 참고 :

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

지금 당신은 당신이 여전히 배경에 텍스트로 채워되는 동안 사용자가 에디터의 내용을 수정하는 경우 수행하려고하는지 스스로에게 물어해야합니다. 여기서는 간단하게 버튼과 편집기 자체를 비활성화합니다 (나머지 UI는 액세스 가능하고 응답 가능합니다). 그러나 질문은 여전히 ​​열려 있습니다. 또한이 샘플의 범위를 벗어나는 일부 취소 논리를 구현해야합니다.

+0

니스! 그러나 나는 질문이있다. 왜 Thread.Sleep 대신 Task.Delay를 사용합니까? – ppk

+0

@ppk의'ReadLineAsync' 구현은 테스트를위한 모형입니다. 여기에 목적을 위해'Task.Run' 람다 안에서'Thread.Sleep'을 사용합니다. 이것은 풀 스레드에게 오프로드되는 텍스트 줄을 생성하는 CPU와 관련된 몇 가지 작업을 나타냅니다. 테스팅 이외에는 실제로 프로덕션 코드에서'Thread.Sleep'을 거의 필요로하지 않을 것입니다. – Noseratio

+2

추가 설명 주셔서 감사합니다. – ppk

1

아마 Microsoft의 반응 프레임 워크를 사용하십시오. 여기에 당신이 필요로하는 코드는 다음과 같습니다

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 교체 추가하기 전에 텍스트 상자를 지울해야 :

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

NuGet "System.Reactive"& "System.Reactive합니다. Windows.Forms "비트를 얻을 수 있습니다.

양식을 닫을 때 subscription.Dispose()을 수행하면 이벤트 처리기가 제거됩니다.

2

UI를 반응 적으로 유지해야하는 경우 숨을 쉬기 만하면됩니다.
한 줄의 텍스트를 읽는 것이 너무 빠르기 때문에 (거의) 기다리는 동안 UI를 업데이트하는 데 더 오래 걸립니다. 지연을 조금이라도 삽입하면 UI가 업데이트됩니다. Task.Factory
TPL을 사용하여 (은 AWAIT SynchronizationContext에 의해 캡처 됨)

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 
     } 
     }); 
    } 
} 
관련 문제