C# - 在创建新控件的线程仍在运行时关闭表单 [英] C# - Closing form while a thread that creates new controls is still running

查看:61
本文介绍了C# - 在创建新控件的线程仍在运行时关闭表单的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在辅助线程上添加自定义控件,但是当我在线程仍在运行时关闭窗口时出现此异常:

<块引用>

Invoke 或 BeginInvoke 不能在控件上调用,直到窗口出现句柄已创建.

我不知道出现这个异常的原因是因为线程使用不当,还是因为我在线程仍在运行时关闭了窗口.

返回搜狐,查看更多 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

这是我得到例外的代码:

panelWall.Invoke(new Action(() =>{postControl = new FBPostUserControl(m_LoggedInUser.Name, m_LoggedInUser.ImageNormal, post.CreatedTime);postControl.PostBody = post.Message;postControl.Image = postImage;postControl.Dock = DockStyle.Top;postControl.BringToFront();}));

这是我的自定义控件的代码:

公共部分类 FBPostUserControl : UserControl{私有只读字符串 m_UserName = string.Empty;私有只读图像 m_UserProfileImage = null;私人只读日期时间?m_DatePosted = null;私有图像 m_Image = null;私人字符串 m_PostBody = string.Empty;公共字符串用户名{得到 { 返回 m_UserName;}}公共日期时间?发布日期{得到 { 返回 m_DatePosted;}}公共形象形象{得到 { 返回 m_Image;}放{如果(值 == 空){pictureBoxImage.Visible = false;}别的{pictureBoxImage.Visible = true;pictureBoxImage.Image = 值;更新图像大小();}}}私有无效 updateImageSize(){if (pictureBoxImage.Image != null){双倍比例 = pictureBoxImage.Image.Width/pictureBoxImage.Image.Height;pictureBoxImage.Height = (int)(pictureBoxImage.Width/ratio);pictureBoxImage.SizeMode = PictureBoxSizeMode.Zoom;}}公共字符串 PostBody{得到 { 返回 m_PostBody;}放{if (string.IsNullOrWhiteSpace(value) == false){labelPostBody.Visible = true;labelPostBody.Text = 值;}别的{labelPostBody.Visible = false;}}}公共图像 UserProfileImage{得到 { 返回 m_UserProfileImage;}}public FBPostUserControl(string i_Name, Image i_ProfileImage, DateTime? i_PostDate){初始化组件();m_UserName = i_Name;m_UserProfileImage = i_ProfileImage;m_DatePosted = i_PostDate;刷新头();}私有无效refreshHeader(){pictureBoxUserImage.Image = m_UserProfileImage;labelName.Text = m_UserName;如果(标签日期!= null){labelDate.Text = m_DatePosted.ToString();}别的{labelDate.Visible = false;}}}

解决方案

12/1/2020 EDIT START

由于 https://getandplay.github.io/2019/05/15/transfer-of-execution-rights-Task-Yield-Dispatcher-Yield/>

等待 System.Windows.Threading.Dispatcher.Yield() 更安全

12/1/2020 编辑开始

首先,我没有看到您在新线程中启动操作,因为 Invoke 方法只是将操作发布到 UI 线程中的调度程序队列.

因此在您的代码中没有真正的多线程,但是在执行操作时,用户有机会发布 CLOSE FORM 窗口消息,并且可以在您的下一次调用之前对其进行处理.因此,为避免出现异常,请在下次调用之前检查表单是否已关闭.

顺便说一句,我认为仅仅为了更新图形元素而启动一个新线程并没有真正的优势,因为最后,它们必须在 UI 线程中更新,而您只是在往返.

如果你有一个很长的图形操作并且你的目标是 NET Framework 4.5 或更高版本,那么标准的方法是等待一个用于长图形操作的异步方法,并在内部每隔一段时间等待 Task.Yield() 来给出用户有机会取消、关闭窗口等

基本上 Task.Yield() 将方法 continuation 发布到 UI 调度器,当它返回时,您可以检查表单并在表单关闭时取消长操作:

 异步任务 LongJustGraphicsOperation(){而(真){//做一些工作并暂停等待 Task.Yield();如果(formIsClosed)中断;}}

Task.Yield() 是旧的 VB doevents 的 Task 版本.

注意.检查 winform 是否关闭有点棘手 检测表单何时关闭

I'm trying to add custom controls on a secondary thread, but when I close the window while the thread is still running i get this exception:

Invoke or BeginInvoke cannot be called on a control until the window handle has been created.

I don't know if the reason for getting this exception is because of a missused thread or becuse I'm closing the window while the thread is still running.

this is the code that i get the exception for:

panelWall.Invoke(new Action(() =>
            {
                postControl = new FBPostUserControl(m_LoggedInUser.Name, m_LoggedInUser.ImageNormal, post.CreatedTime);
                postControl.PostBody = post.Message;
                postControl.Image = postImage;
                postControl.Dock = DockStyle.Top;
                postControl.BringToFront();
            }));

this is the code of my custom control:

public partial class FBPostUserControl : UserControl
{
    private readonly string m_UserName = string.Empty;
    private readonly Image m_UserProfileImage = null;
    private readonly DateTime? m_DatePosted = null;
    private Image m_Image = null;
    private string m_PostBody = string.Empty;

    public string UserName
    {
        get { return m_UserName; }
    }

    public DateTime? DatePosted
    {
        get { return m_DatePosted; }
    }

    public Image Image
    {
        get { return m_Image; }
        set
        {
            if (value == null)
            {
                pictureBoxImage.Visible = false;
            }
            else
            {
                pictureBoxImage.Visible = true;
                pictureBoxImage.Image = value;
                updateImageSize();
            }
        }
    }

    private void updateImageSize()
    {
        if (pictureBoxImage.Image != null)
        {
            double ratio = pictureBoxImage.Image.Width / pictureBoxImage.Image.Height;
            pictureBoxImage.Height = (int)(pictureBoxImage.Width / ratio);
            pictureBoxImage.SizeMode = PictureBoxSizeMode.Zoom;
        }
    }

    public string PostBody
    {
        get { return m_PostBody; }
        set
        {
            if (string.IsNullOrWhiteSpace(value) == false)
            {
                labelPostBody.Visible = true;
                labelPostBody.Text = value;
            }
            else
            {
                labelPostBody.Visible = false;
            }
        }
    }

    public Image UserProfileImage
    {
        get { return m_UserProfileImage; }
    }

    public FBPostUserControl(string i_Name, Image i_ProfileImage, DateTime? i_PostDate)
    {
        InitializeComponent();
        m_UserName = i_Name;
        m_UserProfileImage = i_ProfileImage;
        m_DatePosted = i_PostDate;

        refreshHeader();
    }

    private void refreshHeader()
    {
        pictureBoxUserImage.Image = m_UserProfileImage;
        labelName.Text = m_UserName;

        if (labelDate != null)
        {
            labelDate.Text = m_DatePosted.ToString();
        }
        else
        {
            labelDate.Visible = false;
        }
    }
}

解决方案

12/1/2020 EDIT START

There are problems awaiting Task.Yield because of Dispatcher Priority as mentioned in https://getandplay.github.io/2019/05/15/transfer-of-execution-rights-Task-Yield-Dispatcher-Yield/

It is safer to await System.Windows.Threading.Dispatcher.Yield()

12/1/2020 EDIT START

First, I don't see that you're launching the operation in a new thread, because the Invoke method just post the action to the dispatcher queue in the UI thread.

So there's no real multithreading in your code, but while the action is performed, the user has had the opportunity to post a CLOSE FORM windows message, and it can be processed before your next Invoke. So to avoid the exceptión, check if the form is closed before your next Invoke.

By the way, I believe there's no real advantage in launching a new thread just to update graphic elements, because, at last, they have to be updated in the UI thread, and you're just spending time and resources in the round trip.

If you have a long graphic operation and you are targeting NET Framework 4.5 or higher, the standard way to do it is await an async method for the long graphics operation, and internally await Task.Yield() at intervals to give the user the opportunity to cancel, close the window, etc.

Basically Task.Yield() posts the method continuation to the UI dispatcher, and when it returns, you can check the form and cancel the long operation if the form is closed:

    async Task LongJustGraphicsOperation()
    {
        while (true)
        {
            //do some work and give a pause
            await Task.Yield();
            if (formIsClosed) break;
        }
    }

Task.Yield() is the Task version of the old VB doevents.

Note. Is somewhat tricky to check if a winform is closed Detect when a form has been closed

这篇关于C# - 在创建新控件的线程仍在运行时关闭表单的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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