sqlite查询阻止UI在背景工作人员中
我的Windows Forms应用程序有一个背景工作人员队列,它可以与API调用一起使用,但是查询本地SQLITE数据库正在阻止UI。不久前,我意识到,在操作上调用调用将其运行在UI线程上,因此我将Dowork函数更改为“背景工作人员线程”上的简单函数。
问题在于我的Form1类中的loadItems()
功能。它排队一个背景工人查询SQLite数据库。
什么可能导致UI块,我该更改如何防止它?
我的自定义工作人员类:
using System.ComponentModel;
namespace EveMarket.Tasks
{
public class Worker : BackgroundWorker
{
public Worker(object Sender)
{
this.Sender = Sender;
}
public object Sender { get; set; }
}
}
我的任务管理器类(管理队列)
using System.ComponentModel;
namespace EveMarket.Tasks
{
public class TaskManager
{
public static Queue<Worker> taskQueue = new Queue<Worker>();
public static List<string> errors = new List<string>();
public static void QueueTask(
object item,
Action<object, Worker, DoWorkEventArgs> action,
Action<object, RunWorkerCompletedEventArgs> actionComplete,
Action<RunWorkerCompletedEventArgs> displayError,
Action<object, ProgressChangedEventArgs> progressChanged)
{
using (var worker = new Worker(item))
{
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.ProgressChanged += (sender, args) =>
{
progressChanged.Invoke(sender, args);
};
worker.DoWork += (sender, args) =>
{
action(sender, worker, args);
};
worker.RunWorkerCompleted += (sender, args) =>
{
if(args.Error != null)
{
displayError.Invoke(args);
} else
actionComplete.Invoke(sender, args);
taskQueue.Dequeue();
if (taskQueue.Count > 0)
{
startQueue();
}
};
taskQueue.Enqueue(worker);
if(taskQueue.Count == 1)
{
startQueue();
}
}
}
private static void startQueue()
{
var next = taskQueue.Peek();
next.ReportProgress(0, "");
next.RunWorkerAsync(next.Sender);
}
}
}
数据库服务类用于项目:
using Microsoft.Data.Sqlite;
using Dapper;
namespace EveMarket.Database
{
public class Item
{
public Item() { }
public Item(int type_id, string name)
{
this.type_id = type_id;
this.name = name;
}
public string name { get; set; }
public int type_id { get; set; }
public static void insert(Item item)
{
using (var connection = new SqliteConnection(Connection.ConnectionString))
{
var sql = "insert into item (type_id, name) values (@type_id, @name)";
connection.ExecuteAsync(sql, item);
}
}
public static List<Item> getAll()
{
using (var connection = new SqliteConnection(Connection.ConnectionString))
{
var sql = "select * from item;";
List<Item> items = connection.Query<Item>(sql).ToList();
return items;
}
}
}
}
以及排队这些任务的表格:
using EveMarket.Database;
using EveMarket.Tasks;
using EVEStandard;
namespace EveMarket
{
public partial class Form1 : Form
{
private EVEStandardAPI client;
private List<Item> items;
public Form1()
{
InitializeComponent();
this.items = new List<Item>();
client = new EVEStandardAPI("[email protected]", EVEStandard.Enumerations.DataSource.Tranquility, TimeSpan.FromSeconds(30));
}
private void Form1_Load(object sender, EventArgs e)
{
loadItems();
toolStripProgressBar1.Value = 50;
}
private void updateItemListToolStripMenuItem_Click(object sender, EventArgs e)
{
fetchItems();
}
private void fetchItems()
{
TaskManager.QueueTask(this,
// DoWork
(x, w, e) =>
{
List<long> ids = new List<long>();
for (int i = 1; i < 17; i++)
{
var task = client.Market.ListTypeIdsRelevantToMarketV1Async(10000002, page: i);
var page = task.Result.Model;
ids.AddRange(page);
w.ReportProgress((i * 100) / 16, "Fetching Market Item IDs Page: " + i);
}
List<List<long>> chunks = Utils.ChunkBy(ids, 1000);
int j = 1;
w.ReportProgress((j * 100) / chunks.Count, "Fetching Item Names ");
foreach (var chunk in chunks)
{
var task = client.Universe.GetNamesAndCategoriesFromIdsV3Async(chunk.ConvertAll(i => (int)i));
var names = task.Result.Model;
var types = names.FindAll((item) => item.Category == EVEStandard.Enumerations.CategoryEnum.inventory_type);
foreach (var type in types)
{
Item item = new Item(type.Id, type.Name);
Item.insert(item);
}
w.ReportProgress((j * 100) / chunks.Count, "Fetching Market Item Names Chunk: " + j);
j++;
}
},
// On Complete
(x, e) =>
{
loadItems();
this.toolStripStatusLabel1.Text = "Idle";
this.toolStripProgressBar1.Value = 0;
},
// On Error
(e) =>
{
MessageBox.Show("Error: " + e.Result.ToString());
},
// On Progress Change
(x, e) =>
{
this.toolStripProgressBar1.Value = e.ProgressPercentage;
this.toolStripStatusLabel1.Text = e.UserState.ToString();
});
}
private void loadItems()
{
TaskManager.QueueTask(this,
// DoWork
(x, w, e) =>
{
w.ReportProgress(50, "Loading Items");
List<Item> i = Item.getAll();
w.ReportProgress(100, "Items Loaded");
this.items = i;
},
// On Complete
(x, e) =>
{
foreach (var item in items)
{
itemBindingSource.Add(item);
}
itemBindingSource.EndEdit();
this.toolStripStatusLabel1.Text = "Idle";
this.toolStripProgressBar1.Value = 0;
},
// On Error
(e) =>
{
MessageBox.Show("Error: " + e.ToString());
},
// On Progress Change
(x, e) =>
{
this.toolStripProgressBar1.Value = e.ProgressPercentage;
this.toolStripStatusLabel1.Text = e.UserState.ToString();
});
}
}
}
I've got a BackgroundWorker Queue for my Windows Forms app, which works fine with API calls, but querying a local SQLite database is blocking the UI. I realized a while ago that calling Invoke on an action runs it on the UI thread, so I've changed the DoWork function to be a simple function call on the BackgroundWorker's thread.
The problem is in the loadItems()
function in my Form1 class. It queues a background worker to query the sqlite database.
What could be causing the UI block and what can I change to prevent it?
My custom Worker class:
using System.ComponentModel;
namespace EveMarket.Tasks
{
public class Worker : BackgroundWorker
{
public Worker(object Sender)
{
this.Sender = Sender;
}
public object Sender { get; set; }
}
}
My Task Manager class (manages the queue)
using System.ComponentModel;
namespace EveMarket.Tasks
{
public class TaskManager
{
public static Queue<Worker> taskQueue = new Queue<Worker>();
public static List<string> errors = new List<string>();
public static void QueueTask(
object item,
Action<object, Worker, DoWorkEventArgs> action,
Action<object, RunWorkerCompletedEventArgs> actionComplete,
Action<RunWorkerCompletedEventArgs> displayError,
Action<object, ProgressChangedEventArgs> progressChanged)
{
using (var worker = new Worker(item))
{
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.ProgressChanged += (sender, args) =>
{
progressChanged.Invoke(sender, args);
};
worker.DoWork += (sender, args) =>
{
action(sender, worker, args);
};
worker.RunWorkerCompleted += (sender, args) =>
{
if(args.Error != null)
{
displayError.Invoke(args);
} else
actionComplete.Invoke(sender, args);
taskQueue.Dequeue();
if (taskQueue.Count > 0)
{
startQueue();
}
};
taskQueue.Enqueue(worker);
if(taskQueue.Count == 1)
{
startQueue();
}
}
}
private static void startQueue()
{
var next = taskQueue.Peek();
next.ReportProgress(0, "");
next.RunWorkerAsync(next.Sender);
}
}
}
Database service class for Item:
using Microsoft.Data.Sqlite;
using Dapper;
namespace EveMarket.Database
{
public class Item
{
public Item() { }
public Item(int type_id, string name)
{
this.type_id = type_id;
this.name = name;
}
public string name { get; set; }
public int type_id { get; set; }
public static void insert(Item item)
{
using (var connection = new SqliteConnection(Connection.ConnectionString))
{
var sql = "insert into item (type_id, name) values (@type_id, @name)";
connection.ExecuteAsync(sql, item);
}
}
public static List<Item> getAll()
{
using (var connection = new SqliteConnection(Connection.ConnectionString))
{
var sql = "select * from item;";
List<Item> items = connection.Query<Item>(sql).ToList();
return items;
}
}
}
}
And the Form which is Queuing these tasks:
using EveMarket.Database;
using EveMarket.Tasks;
using EVEStandard;
namespace EveMarket
{
public partial class Form1 : Form
{
private EVEStandardAPI client;
private List<Item> items;
public Form1()
{
InitializeComponent();
this.items = new List<Item>();
client = new EVEStandardAPI("[email protected]", EVEStandard.Enumerations.DataSource.Tranquility, TimeSpan.FromSeconds(30));
}
private void Form1_Load(object sender, EventArgs e)
{
loadItems();
toolStripProgressBar1.Value = 50;
}
private void updateItemListToolStripMenuItem_Click(object sender, EventArgs e)
{
fetchItems();
}
private void fetchItems()
{
TaskManager.QueueTask(this,
// DoWork
(x, w, e) =>
{
List<long> ids = new List<long>();
for (int i = 1; i < 17; i++)
{
var task = client.Market.ListTypeIdsRelevantToMarketV1Async(10000002, page: i);
var page = task.Result.Model;
ids.AddRange(page);
w.ReportProgress((i * 100) / 16, "Fetching Market Item IDs Page: " + i);
}
List<List<long>> chunks = Utils.ChunkBy(ids, 1000);
int j = 1;
w.ReportProgress((j * 100) / chunks.Count, "Fetching Item Names ");
foreach (var chunk in chunks)
{
var task = client.Universe.GetNamesAndCategoriesFromIdsV3Async(chunk.ConvertAll(i => (int)i));
var names = task.Result.Model;
var types = names.FindAll((item) => item.Category == EVEStandard.Enumerations.CategoryEnum.inventory_type);
foreach (var type in types)
{
Item item = new Item(type.Id, type.Name);
Item.insert(item);
}
w.ReportProgress((j * 100) / chunks.Count, "Fetching Market Item Names Chunk: " + j);
j++;
}
},
// On Complete
(x, e) =>
{
loadItems();
this.toolStripStatusLabel1.Text = "Idle";
this.toolStripProgressBar1.Value = 0;
},
// On Error
(e) =>
{
MessageBox.Show("Error: " + e.Result.ToString());
},
// On Progress Change
(x, e) =>
{
this.toolStripProgressBar1.Value = e.ProgressPercentage;
this.toolStripStatusLabel1.Text = e.UserState.ToString();
});
}
private void loadItems()
{
TaskManager.QueueTask(this,
// DoWork
(x, w, e) =>
{
w.ReportProgress(50, "Loading Items");
List<Item> i = Item.getAll();
w.ReportProgress(100, "Items Loaded");
this.items = i;
},
// On Complete
(x, e) =>
{
foreach (var item in items)
{
itemBindingSource.Add(item);
}
itemBindingSource.EndEdit();
this.toolStripStatusLabel1.Text = "Idle";
this.toolStripProgressBar1.Value = 0;
},
// On Error
(e) =>
{
MessageBox.Show("Error: " + e.ToString());
},
// On Progress Change
(x, e) =>
{
this.toolStripProgressBar1.Value = e.ProgressPercentage;
this.toolStripStatusLabel1.Text = e.UserState.ToString();
});
}
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
事实证明,这是runworkercomplet的循环阻止UI。我发现我可以通过工人的执行结果覆盖数据源,这立即起作用。
Turns out it was the RunWorkerCompleted loop blocking the UI. I discovered that I could simply overwrite the datasource with the result of the Worker's execution, which works immediately.