簡體   English   中英

當我使用跨線程時,我的應用程序直到后台操作完成后才響應

[英]When I use cross thread then my application doesn't respond until background operations complete

我正在從.net Framework 2.0中的應用程序開發Windows。

在后台運行一些操作,例如數據庫備份,進度條和標簽文本更新等。但是當我使用跨線程時,我的應用程序直到后台操作完成后才響應(忙圖標)

這是示例代碼

        private void button1_Click(object sender, EventArgs e)
        {

            Thread t = new Thread(new ThreadStart(UpdateInfo));
            t.Start();
        }


        private void UpdateInfo()
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new MethodInvoker(UpdateInfo)); 
            }
            else
            { 
                // send query to database here for taking backup that could take time
                // update progress bar
                //I'm also using sqlconnection InfoMessage here

                label1.Text = "Text upading......
            }
        }
         private void OnInfoMessage(sender As Object, e As SqlInfoMessageEventArgs)
         {
         }

方案:方案是用戶可以取消操作,但由於應用程序未響應而無法取消操作

===============更新代碼=============================== ==========

我的代碼就像

private void btnBackup_Click(object sender, EventArgs e)
{

  Thread t = new Thread(new ThreadStart(MyThreadFunc));
  t.Start();
}


public void MyThreadFunc()
{
    if (this.InvokeRequired) {

        this.Invoke(new MethodInvoker(Backup));
    } else {
        Backup();
    }
}

public void Backup()
{
    string databaseName = cbDatabase.Text;// getting the name of database for backup


    SaveFileDialog1.ShowDialog(); // dialog will open   
    string backupFileName = SaveFileDialog1.FileName; // getting location of backup


    //============ database query==================

    SqlConnection con = new SqlConnection(conString);
    con.FireInfoMessageEventOnUserErrors = true;
    con.InfoMessage += OnInfoMessage;

    con.Open();

    query = string.Format("backup database {0} to disk = {1}", databaseName,backupFileName);
    using (cmd == new SqlCommand(query, con)) {
        cmd.CommandTimeout = 0;
        cmd.ExecuteNonQuery();
    }
    con.Close();

    con.InfoMessage -= OnInfoMessage;
    con.FireInfoMessageEventOnUserErrors = false;

    //============ Database operation end==================

}

private void OnInfoMessage(object sender, SqlInfoMessageEventArgs e)
{

    lblStatusMsg.Text = e.Message; // mostly messages are like. 1 percent complete, 5 percent complete, 11 percent complete


    foreach (SqlError info in e.Errors) {
        if (info.Class > 10) {
                           // errror logging
        } else {
            Regex reger = new Regex("\\d+");
            Match regerMatch = reger.Match(e.Message);

            if (ProgressBar1.Value == 100) {

            } else {

                ProgressBar1.Value = regerMatch.Value;

            }
        }
    }
}

在數據庫操作完成之前不響應問題

Invoke的目的是使代碼在主線程上運行。 因此,您的代碼正在創建一個線程,其全部目的是強制主線程運行所有代碼。

假設您要運行一個線程,該線程在啟動后10秒鍾將更新標簽文本以指示完成。 您仍然需要Invoke標簽更新,但這是唯一應在調用中進行的操作。

在這種情況下,您的線程函數應如下所示:

private void MyThreadFunc()
{
    // do something here
    Thread.Sleep(10000);

    // update the label:
    if (label1.InvokeRequired)
        Invoke(UpdateLabel);
    else
        UpdateLabel();
}

private void UpdateLabel()
{
    label1.Text = "Something was finished.";
}

換句話說,您需要分離出那些必須在主線程上運行的東西(例如更新窗體上控件的任何東西),然后僅Invoke那些位。 其余的應該在Invoke之外進行。


我想我沒有說清楚。

Invoke方法用於在擁有您要調用的控件或窗體的句柄的線程的上下文中執行代碼。 您可以使用它與UI上的控件進行交互,但是您應將其用於此目的。 如果將所有線程的關閉都放在Invoke調用中,則所有線程的代碼都將在UI線程中運行,這使得擁有一個單獨的線程變得毫無意義。

如果您想在事情發生時停止應用程序的UI暫停(畢竟這是使用線程的主要原因之一),那么僅應在絕對必要時使用Invoke方法,然后再對其中很小的部分使用Invoke方法碼。 調用Invoke來更新控件的參數,與表單的非線程安全屬性進行交互等。您可以直接從其他線程使用對話框等,盡管有些人也喜歡使用Invoke

而且,如果您要進行多次調用,則可能應該編寫一些輔助方法來包裝Invoke來清理內容。 就像是:

public void Invoker(Action action)
{
    if (InvokeRequired)
        Invoke(action);
    else
        action();
}

public T Invoker<T>(Func<T> func)
{
    if (InvokeRequired)
        return (T)Invoke(func);
    else
        return func();
}

現在,您可以以最小的影響編寫線程代碼,如下所示:

public void ThreadFunc()
{
    System.Threading.Thread.Sleep(1000);
    Invoker(() => this.label1.Text = "Started");
    for (int i = 1; i < 10; i++)
    {
        System.Threading.Thread.Sleep(1000);
        Invoker(() => this.label1.Text = string.Format("Iteration {0}", i));
    }
    System.Threading.Thread.Sleep(1000);
    Invoker(() => this.label1.Text = "Completed");
}

或者,如果您不喜歡lambda函數(由於某種原因),則可以使用如下方法:

public void Invoker<T>(Action<T> action, T p)
{
    if (InvokeRequired)
        Invoke(action, p);
    else
        action(p);
}

private void SetLabel(string value)
{
    label1.Text = value;
}

然后在您的代碼中:

Invoker(SetLabel, "new text value");

重要的部分是使所調用的代碼很小,否則最終將阻塞主線程。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM