通过嵌套方法使用 TPL 时无法更新 UI 个元素
Unable to update UI elements when using TPL through nested methods
我正在尝试使用 TPL 从多种方法更新 UI 元素。当执行第一个方法时,元素会更新,但是当调用嵌套在第一个方法中的子方法时,我得到一个 InvalidOperationException
-当前的 SynchronizationContext 不能用作 TaskScheduler。我什至尝试将代码转换为 async
- await
模式但没有成功。
编辑 1:无法重现 InvalidOperationException。我现在收到此错误 - 当前的 SynchronizationContext 不能用作 TaskScheduler。
编辑 2:重现了 InvalidOperationException。这是由于当前的 SynchronizationContext 可能未用作 TaskScheduler 引起的。堆栈跟踪如下:
at
System.Threading.Tasks.SynchronizationContextTaskScheduler..ctor()
at
System.Threading.Tasks.TaskScheduler.FromCurrentSynchronizationContext()
at IntegratedTracker.IntegratedTracker.ProgressReporter..ctor() in
at IntegratedTracker.IntegratedTracker.UploadToDb() in
at System.Threading.Tasks.Task.InnerInvoke() at
System.Threading.Tasks.Task.Execute()
此外,我注意到如果我在 button_click 事件中调用 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.
}
});
}
我什至尝试将现有代码转换为使用 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'.
}
我什至搜索了有关 TPL、初学者 TPL 等的教程,但是 none 这对解决我的问题很有帮助。
此外,我想知道我们是否需要处理创建的任务,或者这些任务是否像 using 块一样自动处理。如果在任务完成前遇到异常,是否需要处理任务?
我是 TPL 的新手,我正在为这些概念苦苦挣扎,因此非常感谢您的帮助。
忘了说,我正在使用 VS2010、.Net 4.0,这是用于 Windows Forms 应用程序。
您不想在 MoveValuesToNewDbDeleteFromSourceDb
方法中开始新任务,因为:
The below method should be called in sequence and labels updated in the same sequence.
为什么不将该方法更改为如下所示:
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(() => {
// ...
});
}
}
并这样称呼它:
private void UpdateValuesInDb();
{
// Code for updating the values.
// The below method should be called in sequence
// and labels updated in the same sequence.
// for type "1" .. "4".
MoveValuesToNewDbDeleteFromSourceDb("1", values);
MoveValuesToNewDbDeleteFromSourceDb("2", values);
MoveValuesToNewDbDeleteFromSourceDb("3", values);
MoveValuesToNewDbDeleteFromSourceDb("4", values);
}
注:如博文所述,ProgressReporter
为"deprecated":
Update, 2012-02-16: The information in this post is old. See the new post Reporting Progress from Async Tasks for a better solution.
第二个注意事项:我假设您的 progressReporter
实例是一个成员变量,它是在您的 UI 线程 上创建的 。如果不是这种情况,这就解释了您遇到问题的原因:将其设为成员变量,在创建表单时构造它。
最后,您可以使用更简单的实现来满足您的需求。
public class ProgressReporter
{
private readonly SynchronizationContext _syncContext = SynchronizationContext.Current;
public void ReportProgress(Action progressAction)
{
_syncContext.Post(new SendOrPostCallback(unused => progressAction()), null);
}
}
如果您的应用程序 运行 在 STAThreadApartment 中,那么它可能会发生。
尝试使用 ConfigureAwait(false) 调用您的任务以防止使用默认的 SyncThreadContext。
虽然它可能会改变异步的行为。但如果我的假设是正确的,这可能会解决问题。
Update: You're on .Net 4.0 so ConfigureAwait is out of context.
问题可能是当您从嵌套方法创建任务时,您的 Synchrnoization 上下文可能会设置回 null。您可以从父方法获取当前同步上下文,并为所有嵌套方法使用相同的上下文。
我已经更新了你的代码:
private static void UploadToDb()
{
var currentSyncContext = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(()=>
{
for (int i = 0; i <= maxRecords - 1; i++)
{
progressReporter.ReportProgress(() => {
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(currentSyncContext);
});
}
private void UpdateValuesInDb(SynchronizationContext context);
{
MoveValuesToNewDbDeleteFromSourceDb(values, context);
MoveValuesToNewDbDeleteFromSourceDb(values, context);
label.text="Moving values of type 3...";
MoveValuesToNewDbDeleteFromSourceDb(values, context);
MoveValuesToNewDbDeleteFromSourceDb(values, context); // This call is the final.
}
private void MoveValuesToNewDbDeleteFromSourceDb(string values, SynchronizationContext context)
{
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.
}
}, context);
}
ProgressReporter
必须在 UI 线程上创建。因此,您需要将行 var progressReporter = new ProgressReporter();
从 MoveValuesToNewDbDeleteFromSourceDb
的顶部移动到 UploadToDb
的顶部。
并且强烈考虑升级您的Visual Studio版本。使用 async
/await
和 IProgress<T>
而不是 ProgressReporter
.
要容易得多
我正在尝试使用 TPL 从多种方法更新 UI 元素。当执行第一个方法时,元素会更新,但是当调用嵌套在第一个方法中的子方法时,我得到一个 InvalidOperationException
-当前的 SynchronizationContext 不能用作 TaskScheduler。我什至尝试将代码转换为 async
- await
模式但没有成功。
编辑 1:无法重现 InvalidOperationException。我现在收到此错误 - 当前的 SynchronizationContext 不能用作 TaskScheduler。
编辑 2:重现了 InvalidOperationException。这是由于当前的 SynchronizationContext 可能未用作 TaskScheduler 引起的。堆栈跟踪如下:
at System.Threading.Tasks.SynchronizationContextTaskScheduler..ctor()
at System.Threading.Tasks.TaskScheduler.FromCurrentSynchronizationContext() at IntegratedTracker.IntegratedTracker.ProgressReporter..ctor() in
at IntegratedTracker.IntegratedTracker.UploadToDb() in
at System.Threading.Tasks.Task.InnerInvoke() at System.Threading.Tasks.Task.Execute()
此外,我注意到如果我在 button_click 事件中调用 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.
}
});
}
我什至尝试将现有代码转换为使用 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'.
}
我什至搜索了有关 TPL、初学者 TPL 等的教程,但是 none 这对解决我的问题很有帮助。
此外,我想知道我们是否需要处理创建的任务,或者这些任务是否像 using 块一样自动处理。如果在任务完成前遇到异常,是否需要处理任务?
我是 TPL 的新手,我正在为这些概念苦苦挣扎,因此非常感谢您的帮助。
忘了说,我正在使用 VS2010、.Net 4.0,这是用于 Windows Forms 应用程序。
您不想在 MoveValuesToNewDbDeleteFromSourceDb
方法中开始新任务,因为:
The below method should be called in sequence and labels updated in the same sequence.
为什么不将该方法更改为如下所示:
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(() => {
// ...
});
}
}
并这样称呼它:
private void UpdateValuesInDb();
{
// Code for updating the values.
// The below method should be called in sequence
// and labels updated in the same sequence.
// for type "1" .. "4".
MoveValuesToNewDbDeleteFromSourceDb("1", values);
MoveValuesToNewDbDeleteFromSourceDb("2", values);
MoveValuesToNewDbDeleteFromSourceDb("3", values);
MoveValuesToNewDbDeleteFromSourceDb("4", values);
}
注:如博文所述,ProgressReporter
为"deprecated":
Update, 2012-02-16: The information in this post is old. See the new post Reporting Progress from Async Tasks for a better solution.
第二个注意事项:我假设您的 progressReporter
实例是一个成员变量,它是在您的 UI 线程 上创建的 。如果不是这种情况,这就解释了您遇到问题的原因:将其设为成员变量,在创建表单时构造它。
最后,您可以使用更简单的实现来满足您的需求。
public class ProgressReporter
{
private readonly SynchronizationContext _syncContext = SynchronizationContext.Current;
public void ReportProgress(Action progressAction)
{
_syncContext.Post(new SendOrPostCallback(unused => progressAction()), null);
}
}
如果您的应用程序 运行 在 STAThreadApartment 中,那么它可能会发生。
尝试使用 ConfigureAwait(false) 调用您的任务以防止使用默认的 SyncThreadContext。
虽然它可能会改变异步的行为。但如果我的假设是正确的,这可能会解决问题。
Update: You're on .Net 4.0 so ConfigureAwait is out of context.
问题可能是当您从嵌套方法创建任务时,您的 Synchrnoization 上下文可能会设置回 null。您可以从父方法获取当前同步上下文,并为所有嵌套方法使用相同的上下文。
我已经更新了你的代码:
private static void UploadToDb()
{
var currentSyncContext = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(()=>
{
for (int i = 0; i <= maxRecords - 1; i++)
{
progressReporter.ReportProgress(() => {
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(currentSyncContext);
});
}
private void UpdateValuesInDb(SynchronizationContext context);
{
MoveValuesToNewDbDeleteFromSourceDb(values, context);
MoveValuesToNewDbDeleteFromSourceDb(values, context);
label.text="Moving values of type 3...";
MoveValuesToNewDbDeleteFromSourceDb(values, context);
MoveValuesToNewDbDeleteFromSourceDb(values, context); // This call is the final.
}
private void MoveValuesToNewDbDeleteFromSourceDb(string values, SynchronizationContext context)
{
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.
}
}, context);
}
ProgressReporter
必须在 UI 线程上创建。因此,您需要将行 var progressReporter = new ProgressReporter();
从 MoveValuesToNewDbDeleteFromSourceDb
的顶部移动到 UploadToDb
的顶部。
并且强烈考虑升级您的Visual Studio版本。使用 async
/await
和 IProgress<T>
而不是 ProgressReporter
.