简体   繁体   中英

C# - Closing form while a thread that creates new controls is still running

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM