WPF 链式任务。第二个任务不刷新网格
WPF Chained tasks. 2nd task does not refresh grid
我想在一个 wpf 屏幕上加载 2 个数据网格。第一个将包含装运 header 信息。第二个将包含详细信息和可能的许多记录。我想在后台加载第一个网格,然后显示它。它应该很快出现。我想在加载第一个网格后加载第二个网格。这样用户可以在加载详细信息时浏览 headers。
我可以使用 BackgroundWorker 来完成这项工作,但我正在尝试切换到使用 Tasks,据我所知,这是 newer/better 执行此操作的方法。
这是将任务链接在一起的代码。
Task LoadShipmentsTask = new Task(() =>
{
LoadShipments();
} );
Task LoadShipmentDetailsTask = LoadShipmentsTask.ContinueWith((t) =>
{
LoadShipmentDetails();
} );
LoadShipmentsTask.Start();
以下是任务调用的方法:
void LoadShipments()
{
App.Current.Dispatcher.Invoke((Action)delegate
{
ocShipments = Shipments.RetrieveObservableCollection(false);
txtBlockRecordCount.Text = string.Format("Load of shipments has COMPLETED. {0} records. ", ocShipments.Count.ToString());
});
}
void LoadShipmentDetails()
{
App.Current.Dispatcher.Invoke((Action)delegate
{
ocShipmentDetails = Shipments.RetrieveShipmentDetailObservableCollection();
txtBlockShipmentDetailRecordCount.Text = string.Format("Loading of Shipment Details Complete. {0} detail records loaded.", ocShipmentDetails.Count.ToString());
});
}
我必须使用 ...Dispatcher.Invoke 来获取 UI 线程来更新网格。
两个可观察的 collections 加载正常,但只有第一个网格被刷新。
奇怪的是,这段代码工作了一两次,但并不始终如一。
Datacontext 在任一线程完成之前设置一次。
数据网格的 Itemsource 设置为可观察的 collections。
我想我必须以某种方式通知 UI 线程,但不确定我遗漏了什么。
更新 - 工作代码:
Task.Factory.StartNew(LoadShipments).ContinueWith(
w =>
{
ocShipments.Clear();
// Load from a temporary collection populated in background...
// Forces the UI to update since CollectionChanged is fired
foreach (Shipment s in ocShipmentsTemp)
{
ocShipments.Add(s);
}
txtBlockRecordCount.Text = string.Format("Load of shipments has COMPLETED. {0} records. ", ocShipments.Count.ToString());
txtBlockShipmentDetailRecordCount.Text = string.Format("Loading Shipment Details.");
LoadShipmentDetails();
ocShipmentDetails.Clear();
// Forces the UI to update since CollectionChanged is fired
foreach (ShipmentDetail sd in ocShipmentDetailsTemp)
{
ocShipmentDetails.Add(sd);
}
txtBlockShipmentDetailRecordCount.Text = string.Format("Loading of Shipment Details Complete. {0} detail records loaded.", ocShipmentDetails.Count.ToString());
}
, token, TaskContinuationOptions.None, scheduler);
查看下面的评论,了解我的代码无效的原因。
推荐的任务使用方式是通过 Factory。
System.Threading.Tasks.Factory.StartNew(() =>
{
// my routine
}).
ContinueWith(()=>
{
// my chained routine
});
此外,如果您在第一部分中对委托使用 lambda 表达式,为什么在另一部分中使用 (Action)delegate 而不是:
App.Current.Dispatcher.Invoke(new Action(() =>
{
ocShipments = Shipments.RetrieveObservableCollection(false);
txtBlockRecordCount.Text = string.Format("Load of shipments has COMPLETED. {0} records. ", ocShipments.Count.ToString());
}));
我觉得其他一切都很好。
如果您只是在内部调用 Dispatcher,为什么要使用 Task,您将直接返回到 UI 线程并一个接一个地发送 2 条消息。您应该使用 Dispatcher.BeginInvoke 并放松任务。他们毫无意义。请尝试以下操作:
App.Current.Dispatcher.BeginInvoke((Action)delegate
{
//LoadShipments
ocShipments = Shipments.RetrieveObservableCollection(false);
txtBlockRecordCount.Text = string.Format("Load of shipments has COMPLETED. {0} records. ", ocShipments.Count.ToString());
//LoadShipmentDetails
ocShipmentDetails = Shipments.RetrieveShipmentDetailObservableCollection();
txtBlockShipmentDetailRecordCount.Text = string.Format("Loading of Shipment Details Complete. {0} detail records loaded.", ocShipmentDetails.Count.ToString());
});
更新
顺便说一句,您的问题也可能发生,因为您正在设置集合。当使用 DataBinding 时,你不应该做 Collection = new Collection
你应该总是做 :
Collection.Clear();
Collection.AddRange(new collection);
由于您的网格绑定到集合,如果您将 属性 设置为新集合,您将失去绑定,因为您的网格将保持绑定到旧集合引用并且不会接收更新。
我认为您应该使用以下任务(更新 UI-线程上的网格)
//Get UI-Thread Scheduler
var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew<IEnumerable<Shipments>>(() =>
{
return Shipments.RetrieveObservableCollection(false);
}).ContinueWith((shipmentsTask) =>
{
txtBlockRecordCount.Text = string.Format("Load of shipments has COMPLETED. {0} records. ", shipmentsTask.Result.Count.ToString());
}, scheduler);
很明显,您可以将其与您的第二个任务再次链接起来。请记住在进入后台任务之前检索调度程序
要了解解决方案,您必须知道 运行 任务在后台运行,并且需要在 UI-线程(或另一个线程,如果他们需要进一步处理)。
你的情况只能在延续任务中设置数据到Datagrid
,不能在后台任务
我想在一个 wpf 屏幕上加载 2 个数据网格。第一个将包含装运 header 信息。第二个将包含详细信息和可能的许多记录。我想在后台加载第一个网格,然后显示它。它应该很快出现。我想在加载第一个网格后加载第二个网格。这样用户可以在加载详细信息时浏览 headers。
我可以使用 BackgroundWorker 来完成这项工作,但我正在尝试切换到使用 Tasks,据我所知,这是 newer/better 执行此操作的方法。
这是将任务链接在一起的代码。
Task LoadShipmentsTask = new Task(() =>
{
LoadShipments();
} );
Task LoadShipmentDetailsTask = LoadShipmentsTask.ContinueWith((t) =>
{
LoadShipmentDetails();
} );
LoadShipmentsTask.Start();
以下是任务调用的方法:
void LoadShipments()
{
App.Current.Dispatcher.Invoke((Action)delegate
{
ocShipments = Shipments.RetrieveObservableCollection(false);
txtBlockRecordCount.Text = string.Format("Load of shipments has COMPLETED. {0} records. ", ocShipments.Count.ToString());
});
}
void LoadShipmentDetails()
{
App.Current.Dispatcher.Invoke((Action)delegate
{
ocShipmentDetails = Shipments.RetrieveShipmentDetailObservableCollection();
txtBlockShipmentDetailRecordCount.Text = string.Format("Loading of Shipment Details Complete. {0} detail records loaded.", ocShipmentDetails.Count.ToString());
});
}
我必须使用 ...Dispatcher.Invoke 来获取 UI 线程来更新网格。
两个可观察的 collections 加载正常,但只有第一个网格被刷新。
奇怪的是,这段代码工作了一两次,但并不始终如一。
Datacontext 在任一线程完成之前设置一次。
数据网格的 Itemsource 设置为可观察的 collections。
我想我必须以某种方式通知 UI 线程,但不确定我遗漏了什么。
更新 - 工作代码:
Task.Factory.StartNew(LoadShipments).ContinueWith(
w =>
{
ocShipments.Clear();
// Load from a temporary collection populated in background...
// Forces the UI to update since CollectionChanged is fired
foreach (Shipment s in ocShipmentsTemp)
{
ocShipments.Add(s);
}
txtBlockRecordCount.Text = string.Format("Load of shipments has COMPLETED. {0} records. ", ocShipments.Count.ToString());
txtBlockShipmentDetailRecordCount.Text = string.Format("Loading Shipment Details.");
LoadShipmentDetails();
ocShipmentDetails.Clear();
// Forces the UI to update since CollectionChanged is fired
foreach (ShipmentDetail sd in ocShipmentDetailsTemp)
{
ocShipmentDetails.Add(sd);
}
txtBlockShipmentDetailRecordCount.Text = string.Format("Loading of Shipment Details Complete. {0} detail records loaded.", ocShipmentDetails.Count.ToString());
}
, token, TaskContinuationOptions.None, scheduler);
查看下面的评论,了解我的代码无效的原因。
推荐的任务使用方式是通过 Factory。
System.Threading.Tasks.Factory.StartNew(() =>
{
// my routine
}).
ContinueWith(()=>
{
// my chained routine
});
此外,如果您在第一部分中对委托使用 lambda 表达式,为什么在另一部分中使用 (Action)delegate 而不是:
App.Current.Dispatcher.Invoke(new Action(() =>
{
ocShipments = Shipments.RetrieveObservableCollection(false);
txtBlockRecordCount.Text = string.Format("Load of shipments has COMPLETED. {0} records. ", ocShipments.Count.ToString());
}));
我觉得其他一切都很好。
如果您只是在内部调用 Dispatcher,为什么要使用 Task,您将直接返回到 UI 线程并一个接一个地发送 2 条消息。您应该使用 Dispatcher.BeginInvoke 并放松任务。他们毫无意义。请尝试以下操作:
App.Current.Dispatcher.BeginInvoke((Action)delegate
{
//LoadShipments
ocShipments = Shipments.RetrieveObservableCollection(false);
txtBlockRecordCount.Text = string.Format("Load of shipments has COMPLETED. {0} records. ", ocShipments.Count.ToString());
//LoadShipmentDetails
ocShipmentDetails = Shipments.RetrieveShipmentDetailObservableCollection();
txtBlockShipmentDetailRecordCount.Text = string.Format("Loading of Shipment Details Complete. {0} detail records loaded.", ocShipmentDetails.Count.ToString());
});
更新
顺便说一句,您的问题也可能发生,因为您正在设置集合。当使用 DataBinding 时,你不应该做 Collection = new Collection
你应该总是做 :
Collection.Clear();
Collection.AddRange(new collection);
由于您的网格绑定到集合,如果您将 属性 设置为新集合,您将失去绑定,因为您的网格将保持绑定到旧集合引用并且不会接收更新。
我认为您应该使用以下任务(更新 UI-线程上的网格)
//Get UI-Thread Scheduler
var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew<IEnumerable<Shipments>>(() =>
{
return Shipments.RetrieveObservableCollection(false);
}).ContinueWith((shipmentsTask) =>
{
txtBlockRecordCount.Text = string.Format("Load of shipments has COMPLETED. {0} records. ", shipmentsTask.Result.Count.ToString());
}, scheduler);
很明显,您可以将其与您的第二个任务再次链接起来。请记住在进入后台任务之前检索调度程序
要了解解决方案,您必须知道 运行 任务在后台运行,并且需要在 UI-线程(或另一个线程,如果他们需要进一步处理)。
你的情况只能在延续任务中设置数据到Datagrid
,不能在后台任务