在Windows窗体中运行长任务时保持UI线程响应 [英] Keep UI thread responsive when running long task in windows forms

查看:53
本文介绍了在Windows窗体中运行长任务时保持UI线程响应的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试将大文本文件读取到TextBox中,并在将文件拖到Textbox时保持ui响应.

I am trying to read a large text file into a TextBox and keep the ui responsive when a file is dragged to the textbox.

不能按预期工作,Windows窗体被冻结,似乎只能在读取文件并将内容附加到文本框的任务中工作.

Not works as expected, the windows forms is frozen and only seems working the task reading the file and appending the content to the textbox.

IDE抛出了ContextSwitchDeadLock,但实际上不是错误. 这是一项长期运行的任务.我已经修复了它在异常菜单下更改行为的问题.

A ContextSwitchDeadLock was thrown by the IDE, but not really an error. This is a long running task. I have fixed it changing the behaviour under the exception menu.

感谢JSteward,Peter将代码更改为此.

Thanks to JSteward, Peter changed the code to this.

运行此任务时如何保持ui(主线程)响应? 谢谢.

How I can keep the ui ( main thread) responsive when running this task ? Thanks.

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

推荐答案

有时确实需要在UI线程上执行一些异步的后台操作(例如,语法突出显示,按类型拼写检查等).我不会针对您的特定示例(IMO,人为设计)质疑设计问题-很可能您应该在此处使用MVVM模式-但您当然可以使UI线程保持响应状态.

Sometimes it is indeed required to do some asynchronous, background operation on the UI thread (e.g., syntax highlighting, spellcheck-as-you-type, etc). I am not going to question the design issues with your particular (IMO, contrived) example - most likely you should be using the MVVM pattern here - but you can certainly keep the UI thread responsive.

您可以通过感测任何未决的用户输入并让其进入主消息循环来为其指定处理优先级,从而做到这一点.这是一个完整的,剪切粘贴并运行的示例,该示例根据您要解决的任务在WinForms中执行此操作.请注意await InputYield(token)只是这样做:

You can do that by sensing for any pending user input and yielding to the main message loop, to give it the processing priority. Here's a complete, cut-paste-and-run example of how to do that in WinForms, based on the task you're trying to solve. Note await InputYield(token) which does just that:

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可以访问并响应),但是问题仍然存在.另外,您应该查看实现一些取消逻辑的方法,这些我不在本示例的讨论范围之内.

Now you should ask yourself what you're going to do if user modifies the content of the editor while it's still being populated with text on the background. Here for simplicity I just disable the button and the editor itself (the rest of the UI is accessible and responsive), but the question remains open. Also, you should look at implementing some cancellation logic, which I leave outside the scope of this sample.

这篇关于在Windows窗体中运行长任务时保持UI线程响应的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆