使用 .dll 方法的任务块 UI 个线程
Task blocks UI thread with .dll method
我已经创建了用于将数据导出到 Excel 的应用程序。创建 Excel 文件的时间大约需要 10 分钟,因此我决定在不阻塞 UI 线程的情况下在另一个线程中执行此操作,以便用户可以继续使用应用程序。我有一个用于导出数据的 .dll,我已经在 Winforms 中多次完成相同的操作,完全没有问题。我的 .dll 文件将取消令牌作为参数,因此用户可以随时取消导出,这就是为什么我更喜欢使用此 .dll。
不幸的是,我是 WPF 的新手,所以我不知道如何创建与在 Winforms 中相同的东西。这是我的代码片段:
public CancellationTokenSource cancel_task = new CancellationTokenSource();
private async void Button1_Click(object sender, RoutedEventArgs e)
{
//Doing some work in UI thread before calling Task.Run …
await Task.Run(() =>
{
Export_to_Excel(dict_queries, dtp.Value);
}, cancel_task.Token);
}
void Export_to_Excel(Dictionary<string, int> queries, DateTime? date)
{
try
{
using (var con = new OracleConnection(conn_string))
{
con.Open();
//Fetching DB Command to start with exporting data ...
//Inicializing my .dll method, added as reference to project
var export_xlsx = new Excel_export();
//Here is where It starts to block UI thread
Application.Current.Dispatcher.Invoke(() => { export_xlsx.Export_command(cmd_export,cancel_task.Token);});
}
}
catch (Exception ex)
{
MessageBox.Show (ex.Message);
}
}
如果我只使用这一行export_xlsx.Export_command(cmd_export,cancel_task.Token);
而不是 Application.Current.Dispatcher.Invoke...
,然后我得到“System.Threading.ThreadStateException:在可以进行 OLE 调用之前,必须将当前线程设置为单线程单元 (STA) 模式... “ 错误。这在我看来就像一个 .dll 回到了 UI 线程,即使它是从非 UI 线程调用的......但是这条线在 Winforms 中对我来说非常适用,所以我不知道这里出了什么问题。
我尝试了 Task.Run 或 Task.Factory.StartNew 的许多不同变体(就像我通常在 Winforms 中所做的那样) ,但一切都会导致我阻塞 UI 线程或其他类型的错误。
我的设计主要 Window 带有 Frame,在该 Frame 中我打开一个页面,在其中单击按钮导出数据。如前所述,我是 WPF 的新手,所以这可能是我遇到问题的原因。任何建议都非常感谢!
编辑:
我想我知道出了什么问题。在我的 .dll 代码中,我调用 SaveFileDialog.ShowDialog(),据我所知,这是一个 UI 线程操作。不幸的是,我在这个应用程序中需要它,在这种情况下我能做些什么吗?
我猜这就是为什么在 Winforms 中一切正常的原因,因为我不在那里使用 SaveFileDialog。
这可以解决您的问题:
Excel_export export_xlsx;
private async void Button1_Click(object sender, RoutedEventArgs e)
{
//Doing some work in UI thread before calling Task.Run …
await Task.Run(() =>
{
export_xlsx = new Excel_export();
Export_to_Excel(dict_queries, dtp.Value);
}, cancel_task.Token);
}
void Export_to_Excel(Dictionary<string, int> queries, DateTime? date)
{
try
{
using (var con = new OracleConnection(conn_string))
{
con.Open();
//Fetching DB Command to start with exporting data ...
//Here is where It starts to block UI
export_xlsx.Export_command(cmd_export,cancel_task.Token);
}
}
catch (Exception ex)
{
MessageBox.Show (ex.Message);
}
}
根据您的代码,您同时陷入两个错误:
- 您正在尝试在非 STA 线程中使用 OLE
- 您正在尝试从另一个线程访问 DispatcherObject (SaveFileDialog)
为什么您的原始代码有效(但阻止了 UI)?因为您将 OLE 工作分派给 STA 的 UI 线程,并且从 UI 线程您可以毫无问题地访问 SaveFileDialog。
关于"The calling thread cannot access this object..."错误:
Application.Current.Dispatcher.Invoke
会将工作委托给 UI 线程,因此在您的代码中您正在创建一个线程来使 UI 再次工作;这就是您阻止 UI 的原因。使用 Invoke 仅从另一个线程更新 UI 元素(或任何 DispatcherObject),而不是用于所有工作。如果您尝试在不调用的情况下从另一个线程访问 DispatcherObject(如 UI 控件),您会收到 "The calling thread cannot access this object because a different thread owns It" 错误。
现在关于 "Current Thread must be set to single thread apartment (STA)..." 错误:
尝试设置您正在创建的新线程的单元状态并在 SaveFileDialog 上使用 Invoke:
public static Task<bool> StartSTATask(Action func) {
var tcs = new TaskCompletionSource<bool>();
Thread thread = new Thread(() =>
{
try {
func();
tcs.SetResult(true);
}
catch (Exception e) {
tcs.SetException(e);
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
return tcs.Task;
}
public CancellationTokenSource cancel_task = new CancellationTokenSource();
private async void Button1_Click(object sender, RoutedEventArgs e)
{
//Doing some work in UI thread before calling Task.Run …
try{
await StartSTATask(() => { Export_to_Excel(dict_queries, dtp.Value);});
}
catch(Exception ex){
MessageBox.Show (ex.Message); //lets keep UI things in UI thread
}
}
void Export_to_Excel(Dictionary<string, int> queries, DateTime? date)
{
using (var con = new OracleConnection(conn_string))
{
con.Open();
//Fetching DB Command to start with exporting data ...
//Inicializing my .dll method, added as reference to project
var export_xlsx = new Excel_export();
export_xlsx.Export_command(cmd_export,cancel_task.Token);
}
}
}
//into dll
public void Export_command(cmd_export,cancel_task.Token){
//do work
Application.Current.Dispatcher.Invoke( () => SaveFileDialog.ShowDialog());
}
我已经创建了用于将数据导出到 Excel 的应用程序。创建 Excel 文件的时间大约需要 10 分钟,因此我决定在不阻塞 UI 线程的情况下在另一个线程中执行此操作,以便用户可以继续使用应用程序。我有一个用于导出数据的 .dll,我已经在 Winforms 中多次完成相同的操作,完全没有问题。我的 .dll 文件将取消令牌作为参数,因此用户可以随时取消导出,这就是为什么我更喜欢使用此 .dll。
不幸的是,我是 WPF 的新手,所以我不知道如何创建与在 Winforms 中相同的东西。这是我的代码片段:
public CancellationTokenSource cancel_task = new CancellationTokenSource();
private async void Button1_Click(object sender, RoutedEventArgs e)
{
//Doing some work in UI thread before calling Task.Run …
await Task.Run(() =>
{
Export_to_Excel(dict_queries, dtp.Value);
}, cancel_task.Token);
}
void Export_to_Excel(Dictionary<string, int> queries, DateTime? date)
{
try
{
using (var con = new OracleConnection(conn_string))
{
con.Open();
//Fetching DB Command to start with exporting data ...
//Inicializing my .dll method, added as reference to project
var export_xlsx = new Excel_export();
//Here is where It starts to block UI thread
Application.Current.Dispatcher.Invoke(() => { export_xlsx.Export_command(cmd_export,cancel_task.Token);});
}
}
catch (Exception ex)
{
MessageBox.Show (ex.Message);
}
}
如果我只使用这一行export_xlsx.Export_command(cmd_export,cancel_task.Token);
而不是 Application.Current.Dispatcher.Invoke...
,然后我得到“System.Threading.ThreadStateException:在可以进行 OLE 调用之前,必须将当前线程设置为单线程单元 (STA) 模式... “ 错误。这在我看来就像一个 .dll 回到了 UI 线程,即使它是从非 UI 线程调用的......但是这条线在 Winforms 中对我来说非常适用,所以我不知道这里出了什么问题。
我尝试了 Task.Run 或 Task.Factory.StartNew 的许多不同变体(就像我通常在 Winforms 中所做的那样) ,但一切都会导致我阻塞 UI 线程或其他类型的错误。
我的设计主要 Window 带有 Frame,在该 Frame 中我打开一个页面,在其中单击按钮导出数据。如前所述,我是 WPF 的新手,所以这可能是我遇到问题的原因。任何建议都非常感谢!
编辑:
我想我知道出了什么问题。在我的 .dll 代码中,我调用 SaveFileDialog.ShowDialog(),据我所知,这是一个 UI 线程操作。不幸的是,我在这个应用程序中需要它,在这种情况下我能做些什么吗?
我猜这就是为什么在 Winforms 中一切正常的原因,因为我不在那里使用 SaveFileDialog。
这可以解决您的问题:
Excel_export export_xlsx;
private async void Button1_Click(object sender, RoutedEventArgs e)
{
//Doing some work in UI thread before calling Task.Run …
await Task.Run(() =>
{
export_xlsx = new Excel_export();
Export_to_Excel(dict_queries, dtp.Value);
}, cancel_task.Token);
}
void Export_to_Excel(Dictionary<string, int> queries, DateTime? date)
{
try
{
using (var con = new OracleConnection(conn_string))
{
con.Open();
//Fetching DB Command to start with exporting data ...
//Here is where It starts to block UI
export_xlsx.Export_command(cmd_export,cancel_task.Token);
}
}
catch (Exception ex)
{
MessageBox.Show (ex.Message);
}
}
根据您的代码,您同时陷入两个错误:
- 您正在尝试在非 STA 线程中使用 OLE
- 您正在尝试从另一个线程访问 DispatcherObject (SaveFileDialog)
为什么您的原始代码有效(但阻止了 UI)?因为您将 OLE 工作分派给 STA 的 UI 线程,并且从 UI 线程您可以毫无问题地访问 SaveFileDialog。
关于"The calling thread cannot access this object..."错误:
Application.Current.Dispatcher.Invoke
会将工作委托给 UI 线程,因此在您的代码中您正在创建一个线程来使 UI 再次工作;这就是您阻止 UI 的原因。使用 Invoke 仅从另一个线程更新 UI 元素(或任何 DispatcherObject),而不是用于所有工作。如果您尝试在不调用的情况下从另一个线程访问 DispatcherObject(如 UI 控件),您会收到 "The calling thread cannot access this object because a different thread owns It" 错误。
现在关于 "Current Thread must be set to single thread apartment (STA)..." 错误:
尝试设置您正在创建的新线程的单元状态并在 SaveFileDialog 上使用 Invoke:
public static Task<bool> StartSTATask(Action func) {
var tcs = new TaskCompletionSource<bool>();
Thread thread = new Thread(() =>
{
try {
func();
tcs.SetResult(true);
}
catch (Exception e) {
tcs.SetException(e);
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
return tcs.Task;
}
public CancellationTokenSource cancel_task = new CancellationTokenSource();
private async void Button1_Click(object sender, RoutedEventArgs e)
{
//Doing some work in UI thread before calling Task.Run …
try{
await StartSTATask(() => { Export_to_Excel(dict_queries, dtp.Value);});
}
catch(Exception ex){
MessageBox.Show (ex.Message); //lets keep UI things in UI thread
}
}
void Export_to_Excel(Dictionary<string, int> queries, DateTime? date)
{
using (var con = new OracleConnection(conn_string))
{
con.Open();
//Fetching DB Command to start with exporting data ...
//Inicializing my .dll method, added as reference to project
var export_xlsx = new Excel_export();
export_xlsx.Export_command(cmd_export,cancel_task.Token);
}
}
}
//into dll
public void Export_command(cmd_export,cancel_task.Token){
//do work
Application.Current.Dispatcher.Invoke( () => SaveFileDialog.ShowDialog());
}