C# 通过嵌套方法使用TPL时无法更新UI元素

C# 通过嵌套方法使用TPL时无法更新UI元素,c#,asynchronous,progress-bar,task-parallel-library,C#,Asynchronous,Progress Bar,Task Parallel Library,我正在尝试使用TPL从多个方法更新UI元素。当执行第一个方法时,元素会得到更新,但是当调用第一个方法中嵌套的子方法时,我会得到一个invalidoOperationException-当前SynchronizationContext不能用作TaskScheduler。我甚至尝试将代码转换为async-wait模式,但没有成功 编辑1:无法复制InvalidOperationException。我现在收到此错误-当前SynchronizationContext不能用作TaskScheduler 编

我正在尝试使用TPL从多个方法更新UI元素。当执行第一个方法时,元素会得到更新,但是当调用第一个方法中嵌套的子方法时,我会得到一个
invalidoOperationException
-当前SynchronizationContext不能用作TaskScheduler。我甚至尝试将代码转换为
async
-
wait
模式,但没有成功

编辑1:无法复制InvalidOperationException。我现在收到此错误-当前SynchronizationContext不能用作TaskScheduler

编辑2:复制了InvalidOperationException。这是由于curren SynchronizationContext不能用作TaskScheduler而导致的。堆栈跟踪如下所示:

在 System.Threading.Tasks.SynchronizationContextTaskScheduler..ctor()
在 System.Threading.Tasks.TaskScheduler.FromCurrentSynchronizationContext() 在

位于System.Threading.Tasks.Task.InnerInvoke()的 System.Threading.Tasks.Task.Execute()

另外,我注意到,如果在按钮单击事件中调用UpdateValuesInDb方法,更新将传递到UI

下面是我的代码以及我所做的研究,这些研究在代码的注释中

private void btnUploadToDb_Click(object sender, EventArgs e)
{
    UploadToDb();
}

private static void UploadToDb()
{
    Task.Factory.StartNew(()=>
    {
        for (int i = 0; i <= maxRecords - 1; i++)
        {
            // Code for inserting into Db.

            // Update progress to progressbar and label on UI form.
            // Borrowed from Stephen Cleary's article
            // http://blog.stephencleary.com/2010/06/reporting-progress-from-tasks.html
            progressReporter.ReportProgress(() => { 
                // Can this be done in a better way than enclosing in a 
                // ReportProgress before entering the for loop?                     
                progressbar.maximum=maxRecords; 
                label.text="Uploading " + i;
                progressbar.value=i;
            });
        }

        progressReporter.ReportProgress(() => {
            label.Text = "Updating values in DB, please wait...";
        });

        // When this call is made the subsequent updates to the main form 
        // were not successful due to 'InvalidOperationException'.
        UpdateValuesInDb();     
    });
}

private void UpdateValuesInDb();
{
    // Code for updating the values.

    // The below method should be called in sequence and labels updated in the same sequence.

    // Report progress to UI for values of type 1.
    // label.text="Moving values of type 1..."; // This has not been implemented because of cross thread exceptions
                            // This is what I'd like to achieve.
    MoveValuesToNewDbDeleteFromSourceDb(values); // This call has to complete first

    // Report progress to UI for values of type 2.
    // label.text="Moving values of type 2..."; // This has not been implemented because of cross thread exceptions
                            // This is what I'd like to achieve.
    MoveValuesToNewDbDeleteFromSourceDb(values); // This call has to complete second.

    // Report progress to UI for values of type 3.
    label.text="Moving values of type 3...";    // This has not been implemented because of cross thread exceptions
                            // This is what I'd like to achieve.
    MoveValuesToNewDbDeleteFromSourceDb(values); // This call has to complete third.

    // Report progress to UI for values of type 4.
    // label.text="Moving values of type 4..."; // This has not been implemented because of cross thread exceptions
                            // This is what I'd like to achieve.
    MoveValuesToNewDbDeleteFromSourceDb(values); // This call is the final.
}


private void MoveValuesToNewDbDeleteFromSourceDb(string values)
{
    var progressReporter = new ProgressReporter();
    Task.Factory.StartNew(() => {
        for (int i = 0; i <= dt.Rows.Count - 1; i++)
        {
            // Tried using Stephen Cleary's code here but it fails with 
                    progressReporter.ReportProgress(() =>
                    {
                        progressBar.Maximum = maxRecords - 1;
                        Label.Text = "Uploading " + i;
                        progressBar.Value = i;
                    });
            // Need to update label and progress in for loop as mentioned above.
        }
    });
}
private void btnuploaddtodb\u单击(对象发送方,事件参数e)
{
上传todb();
}
私有静态void UploadToDb()
{
Task.Factory.StartNew(()=>
{
对于(int i=0;i
{
progressBar.Maximum=maxRecords-1;
Label.Text=“上传”+i;
progressBar.Value=i;
});
//如上所述,需要更新for循环中的标签和进度。
}
});
}
我甚至尝试将现有代码转换为使用async await方法,但在编译过程中失败,并声明它是不可等待的。 这就是我试图改变的:

private async void btnUploadToDb_Click(object sender, EventArgs e)
{
    await UploadToDb(); // Got the error Type System.Threading.Tasks.Task is not awaitable.
}

private async Task UploadToDb()
{
    // Codes is same as the previous one, I've just used the async 
    // modifier, though I do not know what to await here.

    // Tried to assign await as follows:
    await   Task.Factory.StartNew(() => {
        for (int i = 0; i <= dt.Rows.Count - 1; i++)
        {
            // Need to update label and progress bar here.
        }
    });

    // The above results in a compile error:
    // 'Type System.Threading.Tasks.Task is not awaitable'.
}
private async void btnuploadtob\u单击(对象发送方,事件参数e)
{
wait UploadToDb();//获取错误类型System.Threading.Tasks.Task不可等待。
}
专用异步任务上载到数据库()
{
//代码与前面的代码相同,我刚刚使用了异步
//修饰符,虽然我不知道在这里等待什么。
//尝试按如下方式分配等待:
等待任务。工厂。开始新建(()=>{

对于(int i=0;i您不想在
MoveValuesToNewDbDeleteFromSourceDb
方法中启动新任务,因为:

应按顺序调用以下方法,并按相同顺序更新标签

为什么不把这个方法改成这样:

private void MoveValuesToNewDbDeleteFromSourceDb(string valuesType, string values)
{
    progressReporter.ReportProgress(() => {
        label.text = string.Format("Moving values of type {0}...", valuesType);
    });

    for (int i = 0; i <= dt.Rows.Count - 1; i++)
    {
        // Do your move values thing.

        // Report progress
        progressReporter.ReportProgress(() => {
           // ...
        });
    }
}
注:如博客中所述,
ProgressReporter
已“弃用”:

更新,2012-02-16:此帖子中的信息是旧的。有关更好的解决方案,请参阅新帖子

第二个注意事项:我假设您的
progressReporter
实例是一个成员变量,它是在您的UI线程上创建的。如果不是这样,这就解释了您遇到问题的原因:将其设为成员变量,在创建表单时构造它。

最后,您可以根据需要使用一个简单得多的实现

public class ProgressReporter
{
    private readonly SynchronizationContext _syncContext = SynchronizationContext.Current;

    public void ReportProgress(Action progressAction)
    {
        _syncContext.Post(new SendOrPostCallback(unused => progressAction()), null);
    }
}

您不想在
MoveValuesToNewDbDeleteFromSourceDb
方法中启动新任务,因为:

应按顺序调用以下方法,并按相同顺序更新标签

为什么不把这个方法改成这样:

private void MoveValuesToNewDbDeleteFromSourceDb(string valuesType, string values)
{
    progressReporter.ReportProgress(() => {
        label.text = string.Format("Moving values of type {0}...", valuesType);
    });

    for (int i = 0; i <= dt.Rows.Count - 1; i++)
    {
        // Do your move values thing.

        // Report progress
        progressReporter.ReportProgress(() => {
           // ...
        });
    }
}
注:如博客中所述,
ProgressReporter
已“弃用”:

更新,2012-02-16:此帖子中的信息是旧的。有关更好的解决方案,请参阅新帖子

第二个注意事项:我假设您的
progressReporter
实例是一个成员变量,它是在您的UI线程上创建的。如果不是这样,这就解释了您遇到问题的原因:将其设为成员变量,在创建表单时构造它。

最后,您可以根据需要使用一个简单得多的实现

public class ProgressReporter
{
    private readonly SynchronizationContext _syncContext = SynchronizationContext.Current;

    public void ReportProgress(Action progressAction)
    {
        _syncContext.Post(new SendOrPostCallback(unused => progressAction()), null);
    }
}

您不想在
MoveValuesToNewDbDeleteFromSourceDb
方法中启动新任务,因为:

应按顺序调用以下方法,并按相同顺序更新标签

为什么不把这个方法改成这样:

private void MoveValuesToNewDbDeleteFromSourceDb(string valuesType, string values)
{
    progressReporter.ReportProgress(() => {
        label.text = string.Format("Moving values of type {0}...", valuesType);
    });

    for (int i = 0; i <= dt.Rows.Count - 1; i++)
    {
        // Do your move values thing.

        // Report progress
        progressReporter.ReportProgress(() => {
           // ...
        });
    }
}
注:如博客中所述,
ProgressReporter
已“弃用”:

更新,2012-02-16:此帖子中的信息是旧的。有关更好的解决方案,请参阅新帖子

第二个注意事项:我假设您的
progressReporter
实例是一个成员变量,它是在您的UI线程上创建的。如果不是这样,这就解释了您遇到问题的原因:将其设为成员变量,在创建表单时构造它。

最后,您可以根据需要使用一个简单得多的实现

public class ProgressReporter
{
    private readonly SynchronizationContext _syncContext = SynchronizationContext.Current;

    public void ReportProgress(Action progressAction)
    {
        _syncContext.Post(new SendOrPostCallback(unused => progressAction()), null);
    }
}

您不想在
MoveValuesToNewDbDeleteFromSourceDb
方法中启动新任务,因为:

应按顺序调用以下方法,并按相同顺序更新标签

为什么不把这个方法改成这样:

private void MoveValuesToNewDbDeleteFromSourceDb(string valuesType, string values)
{
    progressReporter.ReportProgress(() => {
        label.text = string.Format("Moving values of type {0}...", valuesType);
    });

    for (int i = 0; i <= dt.Rows.Count - 1; i++)
    {
        // Do your move values thing.

        // Report progress
        progressReporter.ReportProgress(() => {
           // ...
        });
    }
}
注:如博客中所述,
ProgressReporter
已“弃用”:

更新,2012-02-16:此帖子中的信息是旧的。有关更好的解决方案,请参阅新帖子

第二个注意事项:我假设您的
progressReporter
实例是一个成员变量,它是在您的UI线程上创建的。如果不是这样,这就解释了您遇到问题的原因:将其设为成员变量,在创建表单时构造它。

最后,您可以根据需要使用一个简单得多的实现

public class ProgressReporter
{
    private readonly SynchronizationContext _syncContext = SynchronizationContext.Current;

    public void ReportProgress(Action progressAction)
    {
        _syncContext.Post(new SendOrPostCallback(unused => progressAction()), null);
    }
}
如果你的申请