sqlite查询阻止UI在背景工作人员中

发布于 2025-02-03 00:07:32 字数 8234 浏览 2 评论 0原文

我的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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

っ〆星空下的拥抱 2025-02-10 00:07:32

事实证明,这是runworkercomplet的循环阻止UI。我发现我可以通过工人的执行结果覆盖数据源,这立即起作用。

itemBindingSource.DataSource = e.Result;

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.

itemBindingSource.DataSource = e.Result;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文