从DoWork处理程序调用方法?

时间:2015-03-16 14:28:31

标签: c# backgroundworker

我正在尝试使用BackgroundWorker来完成任务。我让工作者正确运行,在DoWork方法下它然后调用另一个执行的方法然后我面临我的问题:当该方法试图调用另一个方法时它不成功并且不会抛出异常并且我只能看到这是我在BackgroundWorker做错的事情,因为在UI线程上运行以测试方法按预期执行。

这是我管理员工的地方:

private void btnAddShots_Click(object sender, EventArgs e)
{
    backgroundWorker.RunWorkerAsync();     
}

这是我的DoWork方法:

private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    int noOfShots = dataGridShots.Rows.Count - 1;
    int count = 0;

    while (count < noOfShots)
    {
        addTaskPair(dataGridShots.Rows[count].Cells[0].Value.ToString(), 
            dataGridShots.Rows[count].Cells[1].Value.ToString(), 
            dataGridShots.Rows[count].Cells[2].Value.ToString());

        count += 1;
    }
}

以下是我的工作人员调用的addTaskPair方法的精简版本:

private void addTaskPair(string taskName, string taskDescription, string taskPriority)
{
    try
    {
        Task trackingTask = new Task();

        trackingTask.content = taskName;
        trackingTask.description = taskDescription;
        trackingTask.priority = taskPriority; 

        string trackingJson = JsonConvert.SerializeObject(trackingTask);
        trackingJson = "{ \"todo-item\":" + trackingJson + " }";

        string jsonResponse;
        jsonResponse = postJSON(trackingJson, teamworkURL + "/tasklists/" 
            + todoLists.todoLists[cmbTrackingList.SelectedIndex].id + "/tasks.json");
    }
    catch (Exception e)
    {
        debugMessage(e.ToString());
    }
}

在上面的示例中,您会看到我调用方法postJSON,这就是我打到墙上的地方。通过测试,我已经验证了上面的方法运行,但是当从这个线程中调用时,postJSON方法根本不运行。

在研究这个问题时,我看到很多对Invoking的引用,但它们似乎都适用于我不需要做的更改ui控件(尽管使用ProgressChanged {操作进度条{1}}事件)。

如果需要,我可以更多地澄清我的问题,但我真的希望得到这方面的帮助,因为我以前从未成功地使用过背景工作者或线程(我不是专业人士,因为我确信你可以从我的代码中说出来)

2 个答案:

答案 0 :(得分:2)

您正在使用BackgroundWorker.DoWork事件处理程序中的UI控件。别这么做。

在开始BackgroundWorker之前收集数据,并将其作为参数传递给RunWorkerAsync方法。请勿触摸BackgroundWorker.DoWork中的用户界面 - 通过ReportProgress方法进度更新正常。

此外,如果您在.NET 4.5+上运行,则可能需要考虑使用新的Task模式。它仍然需要您事先收集要处理的数据,但使用起来要容易得多:

编辑:正如彼得所说,cmbTrackingList.SelectedIndex发生了无效访问;我已将其包含在下面的代码中。这正是我的原因所在建议使用static方法进行单独线程中发生的操作 - 它会让您更多地考虑与您合作的数据)

var todoList = todoLists.todoLists[cmbTrackingList.SelectedIndex];

var data = 
  dataGridShots
  .Rows
  .Select
   (
     i => 
     new 
     { 
       TaskName = i.Cells[0].Value.ToString(), 
       TaskDescription = i.Cells[1].Value.ToString(), 
       TaskPriority = i.Cells[2].Value.ToString()
     }
   )
  .ToArray();

var result = 
   await Task.Run
   (
     () => 
     foreach (var row in data) 
       handleRowData(row.TaskName, row.TaskDescription, row.TaskPriority, todoList)
   );

既然你已经到目前为止了,你可能会注意到,使postJson方法异步也不会太难(有很多方法可以制作HTTP)请求异步) - 这将允许您使整个代码异步,而不会阻塞任何线程。

多线程很难。始终尝试使用尽可能高的抽象,并避免任何共享状态。如果你需要共享状态,你需要从每个线程同步对它的每一次访问 - 尽量避免这种情况(一个好的做法是让方法在不同的线程上执行 static ,这样你就不会意外地触摸共享状态。)

答案 1 :(得分:1)

根据您的描述,似乎DataGrid组件的访问问题。也就是说,似乎这些语句正确执行,并且addTaskPair()方法被成功调用,但postJSON()方法不是。

鉴于此,我怀疑对cmbTrackingList.SelectedIndex的评估是什么导致异常并中断线程。

也就是说,建议仍然是相同的:在UI线程上保留与UI相关的东西,只运行UI线程之外的其他东西。鉴于你发布的代码,似乎唯一真正应该是异步的(即在后台运行,以免过多延迟UI线程)是对postJSON()的调用。 。据推测这是一个同步网络呼叫,因此可能需要一段时间。其他东西应该确定且快速地运行。

鉴于 ,这里是我如何重构代码,利用新的async / await功能:

private async void btnAddShots_Click(object sender, EventArgs e)
{
    int noOfShots = dataGridShots.Rows.Count - 1;
    int count = 0;

    while (count < noOfShots)
    {
        await addTaskPair(dataGridShots.Rows[count].Cells[0].Value.ToString(), 
            dataGridShots.Rows[count].Cells[1].Value.ToString(), 
            dataGridShots.Rows[count].Cells[2].Value.ToString());

        count += 1;
    }
}

private async Task addTaskPair(string taskName, string taskDescription, string taskPriority)
{
    try
    {
        TaskData trackingTask = new TaskData();

        trackingTask.content = taskName;
        trackingTask.description = taskDescription;
        trackingTask.priority = taskPriority; 

        string trackingJson = JsonConvert.SerializeObject(trackingTask);
        trackingJson = "{ \"todo-item\":" + trackingJson + " }";

        string jsonResponse;
        string url = teamworkURL + "/tasklists/" 
            + todoLists.todoLists[cmbTrackingList.SelectedIndex].id + "/tasks.json";
        jsonResponse = await Task.Run(() => postJSON(trackingJson, url));
    }
    catch (Exception e)
    {
        debugMessage(e.ToString());
    }
}

注意:在上面我将您自己的Task类型的名称更改为TaskData。我强烈建议您选择Task以外的名称,因为在整个现代.NET API中普遍使用.​​NET Task类型。

在上面,大多数代码都将在UI线程上运行。编译器重写async方法以在任何await语句处返回,并在等待Task完成时继续执行该方法。请注意,async方法仅在最终有Task个对象等待时返回;所以在上面,btnAddShots_Click()方法最初会在addTaskPair()方法调用Task.Run()后返回,并且本身已在await语句返回。

重要说明:在此上下文中,从UI线程调用和等待异步方法会导致框架在UI线程上运行方法的其余部分。也就是说,当异步操作完成后,对代码执行的控制将返回到您启动的UI线程。

此功能可以使所有这些功能正常运行,因此确保您了解它非常有用。 :)

使用postJSON()方法创建的Task对象,在单独的线程中执行对Task.Run()的调用。由于这将在UI线程之外执行,因此我在调用Task.Run()之前将其URL参数的计算移动到局部变量,然后将该变量传递给{{1}在任务线程中调用它时的方法。这样做可确保{UI}线程中postJSON()的评估完成。


修改

注意到OP已经评论他使用的是.NET 4而不是4.5(其中cmbTrackingList.SelectedIndex / async功能正式发布),我提供了这个稍微尴尬的替代方案,仍然保留上面优选的4.5兼容版本的执行特性。虽然可以在VS2010上安装await / async功能(以及恕我直言,这是一个更好的方法),但这个替代方案允许&#34;纯&#34; .NET 4代码虽然仍然实现了基本相同的运行时结果。

await

注意:

  • 当前同步上下文的任务调度程序用于每个延续。这确保了继续本身在UI线程上执行,您可以安全地与UI对象进行交互。
  • 这个恕我直言的最尴尬的部分是你的private void btnAddShots_Click(object sender, EventArgs e) { Action<Task> continuation = null; TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext(); int noOfShots = dataGridShots.Rows.Count - 1; int count = 0; // Note that the continuation delegate is chained, attaching itself as // the continuation for each successive task, thus achieving a looping // mechanism. continuation = task => { if (count < noOfShots) { addTaskPair(dataGridShots.Rows[count].Cells[0].Value.ToString(), dataGridShots.Rows[count].Cells[1].Value.ToString(), dataGridShots.Rows[count].Cells[2].Value.ToString()) .ContinueWith(continuation, uiScheduler); count += 1; } } // Invoking the continuation delegate directly gets the ball rolling continuation(null); } private Task addTaskPair(string taskName, string taskDescription, string taskPriority) { try { TaskData trackingTask = new TaskData(); trackingTask.content = taskName; trackingTask.description = taskDescription; trackingTask.priority = taskPriority; string trackingJson = JsonConvert.SerializeObject(trackingTask); trackingJson = "{ \"todo-item\":" + trackingJson + " }"; string url = teamworkURL + "/tasklists/" + todoLists.todoLists[cmbTrackingList.SelectedIndex].id + "/tasks.json"; // NOTE: must explicitly specify TaskScheduler.Default, because // the default scheduler in the context of a Task is whatever the // current scheduler is, which while executing a continuation would // be the UI scheduler, not TaskScheduler.Default. return Task.Factory.StartNew(() => postJSON(trackingJson, url), CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default) .ContinueWith(task => { if (task.Exception != null) { // Task exceptions are wrapped in an AggregateException debugMessage(task.Exception.InnerException.ToString()); } else { string jsonResponse = task.Result; // do something with jsonResponse? } }, TaskScheduler.FromCurrentSynchronizationContext()); } catch (Exception e) { debugMessage(e.ToString()); } } 循环变成while语句,因为循环的执行跨越多个方法调用。
  • 虽然if / async通常会允许正常的异常处理语法,但在使用显式延续时,您没有此选项。但await将包装Task实例中发生的任何异常,您可以使用它来获取真实异常并报告它。
  • AggregateException有一些微妙的行为:它用于运行给定任务的调度程序是TaskFactory.StartNew()。第一次调用TaskScheduler.Current方法时,没有当前任务,因此addTaskPair()返回默认(即线程池)调度程序。但是每次调用TaskScheduler.Current方法时,它都来自任务延续,因此addTaskPair()将返回用于执行延续的调度程序。当然,我们故意使用UI调度程序并使用该调度程序运行新的TaskScheduler.Current任务将失败目的,因为它只是在当前线程上同步执行。所以这里的命令式指定了我们想要的调度程序,即postJSON(),对应于线程池调度程序。

如果没有TaskScheduler.Default / async,那么让事情变得恰到好处就更难了。它在语法上更加冗长,但恕我直言,它仍然是一个相当不错的选择,因为它基本上保留了所需的,必要的代码结构。特别是,您可以在UI线程中保持执行流程,使UI对象的访问变得微不足道,并且仅在需要时分离长时间运行的操作。

(我还应该指出,这个.NET 4版本并不完全是编译器在使用await / async时为您生成的版本。它非常相似,但不是同样,我会指出虽然以这种方式实现一个单await的方法并不是太糟糕,但是如果你想在同一个方法中使用多个延续,它会有点失控。它&# 39;可能,但在那一点上,我认为只需升级到最新版本的C#就会非常引人注目:))。


最后...

如果完成上述所有操作后,您希望坚持使用await,则应该可以通过对代码进行相对简单的更改来避免发生异常:

BackgroundWorker

即。只需使用 int selectedIndex = (int)Invoke((Func<int>)(() => cmbTrackingList.SelectedIndex)); jsonResponse = postJSON(trackingJson, teamworkURL + "/tasklists/" + todoLists.todoLists[selectedIndex].id + "/tasks.json"); 方法在UI线程上调用一个匿名方法,该方法将返回Control.Invoke()的值。 cmbTrackingList.SelectedIndex方法将收到返回的值,然后将其返回给您。由于Control.Invoke()是通用的,因此必须将其Control.Invoke()返回类型强制转换为您知道返回的类型。

这确保仅在UI线程上访问object对象。我还要注意,如果在进行后台处理时(或者特别是),预计此索引不会发生变化,那么另一种替代方法是检索您的cmbTrackingList方法,然后将其传递给btnAddShots_Click()事件处理程序,然后处理程序将其传递给需要它的DoWork方法。

我把这个选项放在最后是因为我真的相信学习addTaskPair() / async功能很重要且值得,而await课程多年来一直很好用,它基本上被新功能所弃用。但我也很乐意承认BackgroundWorker仍然是一种很好的做事方式,并且可以在你的场景中发挥作用。