【问题标题】:UI not updating when TableAdapter.Fill is called from another Thread从另一个线程调用 TableAdapter.Fill 时 UI 不更新
【发布时间】:2014-12-03 11:55:37
【问题描述】:

我正在使用 .NET 4.0C# 开发一个 MDI 应用程序。 每个 MDI 子项都将是一个带有选项卡的表单,其中包含带有 DataGridView 的 GroupBoxes。 我实现了一个用于管理线程的类。

这是我的ThreadManager 类中的StartNewThread 方法

public string StartNewThread(ThreadStart threadMethod, string threadName)
{
    try
    {
        Thread thread = new Thread(() => threadMethod());
        thread.Name = threadName + " (" + _threadCount++.ToString("D4") + ")";
        thread.Start();
        _threadList.Add(thread.Name, thread);

        return thread.Name;
    }
    catch (Exception ex)
    {
        //Log and manage exceptions
    }

    return null;
}

为了创建 DataGridViews,我使用了 Oracle Developer Tools for VS 库中的一些向导组件。因此,在创建了 DataSource 和 DataSet 之后,我使用从 DataSource 树中拖放来拖动表格并自动创建 DataGridViews。

这是实际的工作代码,在子窗体后面,自动创建。

public partial class ScuoleNauticheForm : Form
{
    public ScuoleNauticheForm()
    {
        InitializeComponent();
    }

    private void ScuoleNauticheForm_Load(object sender, EventArgs e)
    {
        // TODO: This line of code loads data into the 'dEVRAC_NauticheDataSet.PERSONALE' table. You can move, or remove it, as needed.
        this.PersonaleTableAdapter.Fill(this.DEVRAC_NauticheDataSet.PERSONALE);
        // TODO: This line of code loads data into the 'dEVRAC_NauticheDataSet.NATANTI' table. You can move, or remove it, as needed.
        this.NatantiTableAdapter.Fill(this.DEVRAC_NauticheDataSet.NATANTI);
        // TODO: This line of code loads data into the 'dEVRAC_NauticheDataSet.SCUOLE' table. You can move, or remove it, as needed.
        this.ScuoleTableAdapter.Fill(this.DEVRAC_NauticheDataSet.SCUOLE);
    }
}

我现在要做的是管理所有在分离线程上的加载/查询/插入/更新/删除操作。现在我尝试创建一个新线程来加载数据。

这是我尝试过的。

public partial class ScuoleNauticheForm : Form
{
    private readonly ThreadManager _threadManager;

    public ScuoleNauticheForm()
    {
        InitializeComponent();
        _threadManager = ThreadManager.GetInstance();
    }

    private void ScuoleNauticheForm_Load(object sender, EventArgs e)
    {
        _threadManager.StartNewThread(LoadData, "LoadData");
    }

    #region DataBind

    private void LoadData()
    {
        // TODO: This line of code loads data into the 'dEVRAC_NauticheDataSet.PERSONALE' table. You can move, or remove it, as needed.
        this.PersonaleTableAdapter.Fill(this.DEVRAC_NauticheDataSet.PERSONALE);
        // TODO: This line of code loads data into the 'dEVRAC_NauticheDataSet.NATANTI' table. You can move, or remove it, as needed.
        this.NatantiTableAdapter.Fill(this.DEVRAC_NauticheDataSet.NATANTI);
        // TODO: This line of code loads data into the 'dEVRAC_NauticheDataSet.SCUOLE' table. You can move, or remove it, as needed.
        this.ScuoleTableAdapter.Fill(this.DEVRAC_NauticheDataSet.SCUOLE);
    }

    #endregion
}

它只适用于一半...没有错误或异常,但如果我以这种方式加载数据,使用不同的Thread,DataGridviews 不会更新,打开表单时我看不到任何数据,即使我移动或调整它的大小。否则,使用自动生成的代码,可以正确填充 DataGridView。 但是,由于向导还在表单中添加了一个导航栏来浏览记录,我注意到它可以工作,因为它计算了正确的记录数,我可以使用箭头(第一个、上一个、下一个、最后一个)来移动记录。

这是显示我的表单的图像。 查看显示正确总记录数 (14) 并允许我浏览它们的导航栏。

我需要使用delegates吗?如果是这样,我认为这将是一团糟......我应该创建多少 delegates 以及这些方法?还是有其他解决方案?

-- 更新 1--

我知道 UI 线程是由 .NET 自动管理的,因此程序员不需要使用代码来管理它们。那么,是否应该是与管理中内置的.NET UI 线程同步的问题呢?可能我Form.Load()发起的线程干扰了.NET管理的UI线程?

-- 更新 2 --

我尝试实现faby提出的解决方案。我用Task 逻辑替换了我的Thread 逻辑。应用程序的行为是相同的,所以使用Thread 的所有内容现在也可以使用Task但问题仍然存在。由于我使用的是 .NET 4.0 而不是 .NET 4.5,因此我无法使用 async 和 await。所以我不知道用这种方法 UI 是否能正常工作。 还有其他适用于 .NET 4.0 的建议吗?

【问题讨论】:

  • 看看我更新的答案,有诀窍!

标签: c# multithreading oracle user-interface datagridview


【解决方案1】:

您是否考虑BackgroundWorker Class 的选项?

实现DoWorkProgressChanged,您可以在DoWork 中执行您在后台线程中执行的操作,在ProgressChanged 中您可以更新UI

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;
            //long running task

        }


        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            //update the UI components
        }

更新 1

另一种解决方案可能是这样的

public Task LoadDataAsync()
{
    return Task.Factory.StartNew( () =>
    {
        //code to fill your datagridview
    });
}

然后

public async Task ChangeUIComponents()
{
    await LoadDataAsync();

    // now here you can refresh your UI elements           
}

更新 2

要在框架 4.0 中使用 async/await,请尝试使用 this NugetPackage (Microsoft.Bcl.Async)

【讨论】:

  • 我对@9​​87654333@ 不是很专业,所以我从Thread 类开始,因为我过去已经使用过。我真的不知道BackgroundWorker 是否会是更好的方法,以及它是否与我的逻辑兼容。还有ThreadPoolTPL... 但无论如何,更改为BackgroundWorker 或其他东西将迫使我也改变我已经制定的逻辑。你确定我使用BackgroundWorker不会遇到同样的问题吗?
  • 我的第二种方法呢?它符合您的需求吗?
  • 我试图用Task 逻辑改变我的Thread 逻辑。我找到了这些文章:TasksTasks vs Threads。似乎Task 方法是进行线程化的现代且最简单的方法。逻辑几乎相同,只是任务没有名称,而是自动生成的唯一标识符号。所以,用TaskManager.StartNewTask 替换我对ThreadManager.StartNewThread 的调用很容易并且有效……但我的问题仍然存在于Task 上!!!
  • 另外,我不能使用 asyncawait,因为 我使用的是 .NET 4.0 而不是 4.5!
  • 我已经更新了我的答案,尝试使用该包以将 async/await 与框架 4.0 一起使用
【解决方案2】:

我终于找到了一个解决方案不使用 async/await 和其他库。 问题是我在一个新的Task 中执行TableAdapterFill() 方法,所以我需要使用InvokeRequired 将绑定源数据源设置为右线程中的DataTable

所以我使用了delegates。我更改了在新任务上调用的方法,并使其调用其他 3 个方法(每个 DataGridView 填充一个)调用 Fill() 实现 InvokeRequired 检查。

现在我看到了 UI 的创建,几秒钟后,我看到了 DataGridViews 的异步填充。

这篇文章很有用:Load data from TableAdapter async

感谢@faby 建议使用任务而不是线程。这不是解决方案,但它是一种更好的线程处理方式。

这是最终的工作代码

public partial class ScuoleNauticheForm : Form
{
    private readonly TaskManager _taskManager;

    public ScuoleNauticheForm()
    {
        InitializeComponent();
        _taskManager = TaskManager.GetInstance();
    }

    private void ScuoleNauticheForm_Load(object sender, EventArgs e)
    {
        _taskManager.StartNewTask(LoadData);
    }

    #region Delegates

    public delegate void FillPersonaleCallBack();
    public delegate void FillNatantiCallBack();
    public delegate void FillScuoleCallBack();

    #endregion

    #region DataBind

    private void LoadData()
    {
        FillPersonale();
        FillNatanti();
        FillScuole();
    }

    public void FillPersonale()
    {
        if (PersonaleDataGridView.InvokeRequired)
        {
            FillPersonaleCallBack d = new FillPersonaleCallBack(FillPersonale);
            Invoke(d);
        }
        else
        {
            this.PersonaleTableAdapter.Fill(this.DEVRAC_NauticheDataSet.PERSONALE);
        }
    }

    public void FillNatanti()
    {
        if (NatantiDataGridView.InvokeRequired)
        {
            FillNatantiCallBack d = new FillNatantiCallBack(FillNatanti);
            Invoke(d);
        }
        else
        {
            this.NatantiTableAdapter.Fill(this.DEVRAC_NauticheDataSet.NATANTI);
        }
    }

    public void FillScuole()
    {
        if (ScuoleDataGridView.InvokeRequired)
        {
            FillScuoleCallBack d = new FillScuoleCallBack(FillScuole);
            Invoke(d);
        }
        else
        {
            this.ScuoleTableAdapter.Fill(this.DEVRAC_NauticheDataSet.SCUOLE);
        }
    }

    #endregion
}

-- 更新 1--

如果新Task调用的方法是void并且没有任何参数,你可以使用Invoke((MethodInvoker) MethodName)对上面的代码进行一点简化。应用程序的行为是相同的。

这是代码的简化版本

public partial class ScuoleNauticheForm : Form
{
    private readonly TaskManager _taskManager;

    public ScuoleNauticheForm()
    {
        InitializeComponent();
        _taskManager = TaskManager.GetInstance();
    }

    private void ScuoleNauticheForm_Load(object sender, EventArgs e)
    {
        _taskManager.StartNewTask(LoadData);
    }

    #region DataBind

    private void LoadData()
    {
        // Since Fill Methods are void and without parameters,
        // you can use the Invoke method without the need to specify delegates.
        Invoke((MethodInvoker)FillPersonale);
        Invoke((MethodInvoker)FillNatanti);
        Invoke((MethodInvoker)FillScuole);
    }

    public void FillPersonale()
    {
        this.PersonaleTableAdapter.Fill(this.DEVRAC_NauticheDataSet.PERSONALE);
    }

    public void FillNatanti()
    {
        this.NatantiTableAdapter.Fill(this.DEVRAC_NauticheDataSet.NATANTI);
    }

    public void FillScuole()
    {
        this.ScuoleTableAdapter.Fill(this.DEVRAC_NauticheDataSet.SCUOLE);
    }

    #endregion
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-11
    • 1970-01-01
    • 2019-06-30
    • 2014-12-16
    • 1970-01-01
    相关资源
    最近更新 更多