【问题标题】:BackgroundWorker Crashes programBackgroundWorker 崩溃程序
【发布时间】:2014-07-24 21:41:11
【问题描述】:

调用 runworkerasync 的事件:

 private void textBox1_TextChanged(object sender, EventArgs e)
    {
        if (textBox1.Text.Length <= 3)
        {
            _ishouldnteventrytoupdate = true;
            return;
        }
        _ishouldnteventrytoupdate = false;

        _updated = false;
        backgroundWorker1.RunWorkerAsync();
    }

DoWork 函数:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
      //  MessageBox.Show("hallo");
       // if (_ishouldnteventrytoupdate) return;
        //if (_updated) return;


         allArtikels = DatabaseConn.GetAllArtikelsArrayList(false, textBox1.Text);
         //backgroundWorker1.ReportProgress(100);


    }

DatabaseConn 是一个静态类,这是被调用的函数:

public static ArrayList GetAllArtikelsArrayList(bool showAll,String naamZoeker = "")
    {
 SetupConnection();
        _objConnection.Open();
        var allArtikels = new ArrayList();

        var  strSql = "SELECT ";
        if (!showAll)
        {
            strSql += "TOP 10";
        }
        strSql+="  HOOFDGROEP.HOOFDGROEP, SUBGROEP.SUBGROEP, Artikels.*" +
                               " FROM (Artikels LEFT JOIN HOOFDGROEP ON Artikels.HOOFDGROEPID = HOOFDGROEP.ID)" +
                               " LEFT JOIN SUBGROEP ON Artikels.SUBGROEPID = SUBGROEP.ID WHERE (ArtikelNaam LIKE  '%' + @ArtikelNaam + '%');";

        var objCommand = new OleDbCommand(strSql, _objConnection);
        objCommand.Parameters.Add("@ArtikelNaam", OleDbType.Char).Value = naamZoeker;

        try
        {
            var objReader = objCommand.ExecuteReader();
            if (objReader != null && objReader.HasRows)
            {
                while (objReader.Read())
                {

                    var objArtikel = new Artikel
                    {

                        SubGroepId = IntDbTester(objReader["SubgroepId"]),
                        HoofdGroepId = IntDbTester(objReader["HoofdgroepId"]),
                        AdviesPrijsIncl = DecimalDbTester(objReader["ArtikelAdviesPrijsIncl"]),
                        AdviesPrijsExcl = DecimalDbTester(objReader["ArtikelAdviesPrijsExcl"]),
                        BtwPercentage = IntDbTester(objReader["ArtikelBtwPercentage"]),
                        Naam = StringDbTester(objReader["ArtikelNaam"]),
                        OldId = StringDbTester(objReader["ArtikelOldId"]),
                        Id = IntDbTester(objReader["ArtikelId"]),
                        Voorraad = DoubleDbTester(objReader["ArtikelVoorraad"])
                    };
                    allArtikels.Add(objArtikel);

                }
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.ToString());
        }

        _objConnection.Close();
        return allArtikels;

IntDbTesters 、 Stringdbtester 等函数看起来像这样:

public static int IntDbTester(object s)
    {
        return s == DBNull.Value ? 0 : Convert.ToInt32(s);
    }

SetupConnection() 看起来像这样:

 private static void SetupConnection()
    {
        const string strConnection = "Provider=Microsoft.ACE.OLEDB.12.0;" +
                                     "Data Source='Klanten.accdb';" +
                                     "Persist Security Info=False";
        _objConnection = new OleDbConnection(strConnection);
    }

除了 catch(以前从未调用过,所以我不认为使用 backgroundworker 会突然调用它)在 backgroundworker 中没有一个 UI,但是当我输入超过 3 个时,我的应用程序会永远冻结文本框中的字母。

可能是什么原因造成的?

编辑: 例外是:

System.dll 中发生了“System.InvalidOperationException”类型的未处理异常附加信息:Deze BackgroundWorker is momenteel bezet en kan niet meerdere take tegelijk uitvoeren。大致翻译为 backgorundworker 很忙,不能同时执行多个任务

if ((backgroundWorker1 != null) &&!backgroundWorker1.IsBusy)backgroundWorker1.RunWorkerAsync();

暂时解决这个问题,但我希望它实际上取消当前的工作并开始一个新的......

【问题讨论】:

  • 您使用的是哪个 .net 版本?
  • 一个后台工作者不能同时运行多个任务。有多种方法可以同时运行多次您的工作,但首先:考虑一下您的计划。每次您在文本框中输入内容时,您都在尝试启动一个线程,该线程将建立连接、检索结果并将结果分配给同一个变量。当多个线程同时尝试写入变量时会发生什么?在线程内建立 sql 连接需要小心处理。为什么不一次获取数据并在内存中过滤而不是每次都查询?
  • @zaitsman .NET Framework 4.5.1 与 Windows 8.1 一起安装
  • 我希望这会发生(可能有点天真)是当前的后台工作人员被取消,它会再次尝试使用更新的值进行搜索
  • @MoSlo,坏主意 - 如果数据多于内存怎么办

标签: c# database backgroundworker freeze


【解决方案1】:

我可以看到您的代码存在几个问题。但是,最大的问题是您试图从非 UI 线程读取 UI 组件,即

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
  //  MessageBox.Show("hallo");
   // if (_ishouldnteventrytoupdate) return;
    //if (_updated) return;


     allArtikels = DatabaseConn.GetAllArtikelsArrayList(false, textBox1.Text);
     //backgroundWorker1.ReportProgress(100);


}

你应该在你打电话给RunWorkerAsync时传递你的文字

backgroundWorker1.RunWorkerAsync(textBox1.Text);
...
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
     allArtikels = DatabaseConn.GetAllArtikelsArrayList(false, (string)e.Argument);
}

现在您已经发现了您收到的异常,很明显该问题与在工作人员忙时尝试调用 RunWorkerAsync 有关。为了有效地停止/重新启动工作人员,您需要启用取消支持。例如

private void Search()
{
    backgroundWorker1.RunWorkerAsync(textBox1.Text);
}
...
backgroundWorker1.WorkerSupportsCancellation = true;
backgroundWorker1.Completed += (sender, e) {
    if (e.Cancelled) {
        // restart background worker 
        Search();
    }
};
...
private void textBox1_TextChanged(object sender, EventArgs e)
{
    if (backgroundWorker1.IsBusy) {
        backgroundWorker1.CancelAsync();
    } else {
        Search();
    }
}

假设您在此处遵循预先输入的类型行为,每次击键 触发数据库查询并不是一个好主意。一个更好的想法是尝试预测用户何时完成输入并然后执行查询 - 这应该会减少访问数据库的次数,这意味着更有效地使用资源和更好的整体用户体验。一种简单但有效的方法是设置一个计时器,每次用户编辑文本时您都会重置该计时器,并且仅在达到计时器超时时触发查询。


如果您需要在不等待前一个工作人员的情况下启动新查询,那么我建议您使用TPL 而不是BackgroundWorker

private CancellationTokenSource cancellationTokenSource;
...
private void Search()
{
    if (this.cancellationTokenSource != null)
        this.cancellationTokenSource.Cancel();   

    // Create a new cancellation token for the new task.
    this.cancellationTokenSource = new CancellationTokenSource();
    CancellationToken cancellationToken = this.cancellationTokenSource.Token;

    // Start the new task.
    var text = textBox1.Text;
    Task.Run(() => 
    {
        return DatabaseConn.GetAllArtikelsArrayList(false, text); 
    }, cancellationToken)
    .ContinueWith((t) => 
    {
        // do something with t.Result if it wasn't cancelled
    }, cancellationToken, TaskContinuationOptions.NotOnCancelled, TaskScheduler.FromCurrentSynchronizationContext());

【讨论】:

  • 好点,将 textBox1.Text 更改为 const 字符串(用于测试目的)甚至无法修复它,所以这不可能是崩溃者
  • @Boyen 如果它崩溃那么你应该有一个异常消息,你可以发布它吗?
  • 在 worker 中从 textBox1 读取值不会导致崩溃并且可以正常工作。如果您尝试为文本框分配值,则会导致跨线程错误
  • System.dll 中发生了“System.InvalidOperationException”类型的未处理异常 附加信息:Deze BackgroundWorker is momenteel bezet en kan niet meerdere take tegelijk uitvoeren。大致翻译为 backgorundworker 很忙,无法同时执行多个任务,尽管在主要问题的 cmets 中我注意到 if ((backgroundWorker1 != null) &amp;&amp;!backgroundWorker1.IsBusy)backgroundWorker1.RunWorkerAsync(); 暂时修复了这个问题,但预期的结果是它取消了当前的工作并开始一个新的
  • @Tsukasa 您不应尝试从非 UI 线程访问任何类型的 UI 组件,因为 UI 组件具有线程关联性。如果您有一些文件来支持您的声明,我很乐意阅读。
【解决方案2】:

我认为这是导致您崩溃的原因,但在不了解您的应用程序工作流程的情况下很难判断

private void textBox1_TextChanged(object sender, EventArgs e)
{
    if (textBox1.Text.Length <= 3)
    {
        _ishouldnteventrytoupdate = true;
        return;
    }
    _ishouldnteventrytoupdate = false;

    _updated = false;
    backgroundWorker1.RunWorkerAsync(); //THIS IS YOUR ISSUE
}

如果您的 Worker 已经在运行,而您再次调用 RunWorkerAsync 将会导致崩溃。特别是因为您没有赶上或检查工人是否已经很忙。

【讨论】:

  • 更新了主要问题,这确实是问题所在,但修复它引发了另一个问题,无论如何都赞成
  • 我会以不同的方式处理这个问题。每次用户更改文本时,我都不会进行 SQL 调用。
  • 那你会怎么处理呢?它应该是某种“实时搜索”
【解决方案3】:

http://msdn.microsoft.com/en-us/library/h01xszh2(v=vs.110).aspx

你不能打电话

backgroundWorker1.RunWorkerAsync();

在每个 textBox1_TextChanged 上,它会超过您的 3 个字母限制。你应该这样做的方式是:

/// Track number of request you will post to DB. Because it is a bad idea to create many concurrent connections and there is limit in your connection pool.
static const ulong THRESHOLD= 5; // Put your number here.
object syncObjetc = new object();    // Sync all reads/writes to your counter.
volatile ulong counter = 0;

private void textBox1_TextChanged(object sender, EventArgs e)
{
    if (textBox1.Text.Length <= 3)
    {
        _ishouldnteventrytoupdate = true;
        return;
    }
    _ishouldnteventrytoupdate = false;

    _updated = false;

    lock(syncObjetc){
        if (++counter >= THRESHOLD){
              textBox1.Enabled = false;
        }
    }

    ThreadPool.QueueUserWorkItem(new WaitCallback(backgroundWorker1_DoWork), textBox1.Text);
}

private void backgroundWorker1_DoWork(object state)
{
    String text = (String)state;
    // Please use List<Artikel> as return value.
    allArtikels = DatabaseConn.GetAllArtikelsArrayList(false, text);

    lock(syncObjetc){
        if (--counter < THREASHOLD){
            textbox1.BeginInvoke(compareAndUseResult, new object[]{text, allArtikels});
        }
    }
}

private void compareAndUseResult(String query, List<Artikel> result){
    // There is no any warranty about when we will finish and if some new text was already used. It still an option for optimization for you.
    if (query != textbox1.Text)
    {
        // Result is outdated and we do not need it anymore.
        return;
    }

    // use your result to display!
}

请记住,这只是一个草图,但它应该给你一个你应该遵循的方法。 并且可以选择使用 Task 和 CancellationToken 来实现它,以减少过时查询时的无用工作量。

【讨论】:

  • -1 锁定对于这类问题来说是一个糟糕的解决方案,而且很少有场景应该使用volatile 关键字,这肯定不是。
  • 但是你应该锁定,没有其他方法可以执行此阈值检查。
  • 你一定是在开玩笑,锁是最便宜的解决方案,而且比创建新任务便宜得多。
  • 锁定绝对不是一个便宜的解决方案,它会阻塞整个上下文。我无法评论生成lockTask 创建是否更便宜,但是,它们是完全不同的两件事,在你的例子中......你两者都做,这是不必要的。此外,Task 的构造通常很轻,因为它们重用了 ThreadPool 中的现有线程。
  • 锁在许多情况下可能非常便宜,因为如果等待时间很短,它会在内部使用自旋锁,甚至会通过复制几个字节来获取取消令牌。一个主要问题 - 如果文本更改,您将不会搜索新查询,只会忽略文本更改!!!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-30
  • 2011-10-16
  • 2020-03-14
  • 2015-08-05
  • 1970-01-01
相关资源
最近更新 更多