简体   繁体   English

从另一个线程调用TableAdapter.Fill时,UI不更新

[英]UI not updating when TableAdapter.Fill is called from another Thread

I'm developing an MDI application in C# with .NET 4.0 . 我正在使用.NET 4.0C#中开发MDI应用程序。 Each MDI child will be a form with tabs that contains GroupBoxes with a DataGridView . 每个MDI子级都是一个带有选项卡的表单,其中的选项卡包含带有DataGridView GroupBox。 I implemented a class that is used to manage Threads. 我实现了一个用于管理线程的类。

This is the StartNewThread method in my ThreadManager class 这是我的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;
}

To create the DataGridViews I used some Wizard component from Oracle Developer Tools for VS library. 为了创建DataGridView,我使用了Oracle Developer Tools for VS库中的一些向导组件。 So, after creating the DataSource and so the DataSet, then I used drag&drop from DataSource tree to drag tables and automatically create DataGridViews. 因此,在创建了数据源以及数据集之后,我使用了从数据源树中拖放的方式来拖动表并自动创建DataGridViews。

This is the actual working code , behind the child form, automatically created. 这是自动创建的子窗体后面的实际工作代码

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);
    }
}

What I want to do now is manage all the load/query/insert/update/delete operations on separated threads . 我现在想做的是管理分离线程上的所有load / query / insert / update / delete操作 For now I tried to create a new Thread to load the data. 现在,我试图创建一个新的线程来加载数据。

This i what I tried. 这是我尝试过的。

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
}

It works only for half... There's no errors or exceptions, but if I load data that way, using a different Thread , the DataGridviews doesn't update and I don't see any data when opening the form, even if I move or resize it. 它只能工作一半...没有错误或异常,但是如果我使用其他Thread那样加载数据,则DataGridviews不会更新,即使打开窗体,打开窗体时也看不到任何数据或调整大小。 Otherwise, using the automatically generated code, the DataGridViews are populated correctly. 否则,使用自动生成的代码,将正确填充DataGridView。 But, since the wizard also add a navigation bar to the form to navigate through records, I noticed that it works, because it counts the correct number of records and I can use the arrows (first, previous, next, last) to move across records. 但是,由于向导还向表单添加了导航栏以浏览记录,因此我注意到它是可行的,因为它计算了正确的记录数,因此我可以使用箭头(第一,上一个,下一个,最后一个)进行移动记录。

Here is an image showing my form. 这是一张显示我的表格的图像。 See the navigation bar that is showing the correct number of total records (14) and allows me to navigate through them. 请参阅导航栏,它显示正确总数的记录(14),并允许我浏览它们。

请查看显示正确总数记录的导航栏,并允许我浏览它们。

Do I need to use delegates ? 我需要使用delegates吗? If so, I think it would be a mess... how many delegates should I create and for those methods? 如果是这样,我认为那将是一团糟...我应该为这些方法创建多少个delegates Or is there another solution? 还是有其他解决方案?

-- UPDATE 1 -- -更新1-

I know that UI threads are automatically managed by .NET and so the programmer don't need to manage them with code. 我知道UI线程是由.NET自动管理的,因此程序员无需使用代码来管理它们。 So, should it be a problem of synchronization with the .NET UI thread built in management? 那么,这是否是与管理内置的.NET UI线程同步的问题? Maybe my thread launched by Form.Load() interferes with the UI thread managed by the .NET? 也许由Form.Load()启动的线程会干扰.NET管理的UI线程?

-- UPDATE 2 -- -更新2-

I tried to implement the solution proposed by faby. 我试图实施faby提出的解决方案。 I replaced my Thread logic with Task logic. 我用Task逻辑替换了Thread逻辑。 The behaviour of the application is the same, so everything that was working with Thread is now working also with Task . 应用程序的行为是相同的,因此使用Thread内容现在也都可以使用Task But the problem still remains. 但是问题仍然存在。 Since I'm on .NET 4.0 and not .NET 4.5, I could not use async and await. 由于我使用的是.NET 4.0,而不是.NET 4.5,因此无法使用async和await。 So I don't know if with that approach the UI will work correctly or not. 因此,我不知道该方法是否可以正确使用UI。 Any other suggestion valid for .NET 4.0 ? 还有其他建议适用于.NET 4.0吗?

do you consider the option of BackgroundWorker Class ? 您是否考虑使用BackgroundWorker类的选项?

implementing DoWork and ProgressChanged you can do in DoWork what you are doing in background thread and in ProgressChanged you can update the UI 实现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
        }

update 1 更新1

another solution could be something like this 另一个解决方案可能是这样的

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

then 然后

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

    // now here you can refresh your UI elements           
}

update 2 更新2

to use async/await with framework 4.0 try with this NugetPackage ( Microsoft.Bcl.Async ) 在框架4.0上使用async / await尝试与 NugetPackage( Microsoft.Bcl.Async

I finally found a solution without using async/await and other libraries. 我终于找到了不使用async / await和其他库的解决方案。 The problem was that I was executing the Fill() method of TableAdapter inside a new Task and so I needed to use InvokeRequired to set the binding source data source to the DataTable within the right thread. 问题是我正在一个新Task中执行TableAdapterFill()方法,因此我需要使用InvokeRequired在正确的线程中将绑定源数据源设置为DataTable

So I used delegates . 所以我用了delegates I changed the method called on the new Task and make it call 3 other methods (one for each DataGridView to fill) that call Fill() implementing the InvokeRequired check. 我更改了在新Task上调用的方法,并使其调用其他3个方法(每个DataGridView都填充一个),这些方法调用Fill()实现InvokeRequired检查。

Now I see the creation of the UI and then, after a couple of seconds, the asynchronous filling of the DataGridViews. 现在,我看到了UI的创建,然后几秒钟后,异步填充了DataGridViews。

This article was useful: Load data from TableAdapter async 本文很有用: 从TableAdapter异步加载数据

Thanks to @faby for the suggestion to use Task instead of Thread. 感谢@faby建议使用Task而不是Thread。 It was not the solution but it is a better way to do Threading. 这不是解决方案,但它是进行线程化的更好方法。

Here's the final working code . 这是最终的工作代码

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
}

-- Update 1 -- -更新1-

If the methods to call by the new Task are void and without any parameters, you can simplify a bit the above code by using Invoke((MethodInvoker) MethodName) . 如果新Task调用的方法void且没有任何参数,则可以使用Invoke((MethodInvoker) MethodName)简化上述代码。 The behaviour of the application is the same. 该应用程序的行为是相同的。

Here's the simplified version of the code . 这是代码简化版本

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
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM