关于winforms:表单上的C#线程

C# thread on forms

本问题已经有最佳答案,请猛点这里访问。

Possible Duplicate:
How to update GUI from another thread in C#?

我现在有一个C程序来运行一个查询并在一个datagridview中显示结果。

由于记录大小的原因,查询需要运行一段时间(20-30秒)。

我想我会添加一个动画,这样用户至少知道软件正在运行,并且没有停止工作。

当然,当调用过程时,我不能运行任何东西,所以我研究了线程。

这是我的代码(请原谅,我还没有真正发表评论):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Data.Sql;
using System.Data.SqlClient;
using System.Threading;

namespace RepSalesNetAnalysis
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            pictureBox2.Visible = false;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            GetsalesFigures();  
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            AutofillAccounts();
        }


        private void GetsalesFigures()
        {
            try
            {
                string myConn ="Server=herp;" +
                           "Database=shaftdata;" +
                           "uid=fake;" +
                           "pwd=faker;" +
                           "Connect Timeout=120;";

                string acct;// test using 1560
                SqlConnection conn = new SqlConnection(myConn);
                SqlCommand Pareto = new SqlCommand();
                BindingSource bindme = new BindingSource();
                SqlDataAdapter adapt1 = new SqlDataAdapter(Pareto);
                DataSet dataSet1 = new DataSet();
                DataTable table1 = new DataTable();

                Thread aniSql = new Thread(new ThreadStart(animateIcon));//CREATE THE THREAD


                acct = accCollection.Text;

                string fromDate = this.dateTimePicker1.Value.ToString("MM/dd/yyyy");
                string tooDate = this.dateTimePicker2.Value.ToString("MM/dd/yyyy");

                Pareto.Connection = conn;
                Pareto.CommandType = CommandType.StoredProcedure;
                Pareto.CommandText ="dbo.GetSalesParetotemp";
                Pareto.CommandTimeout = 120;

                Pareto.Parameters.AddWithValue("@acct", acct);
                Pareto.Parameters.AddWithValue("@from", fromDate);
                Pareto.Parameters.AddWithValue("@too", tooDate);

                aniSql.Start();                //START THE THREAD!
                adapt1.Fill(dataSet1,"Pareto");
                aniSql.Abort();                //KILL THE THREAD!
                //pictureBox2.Visible = false;

                this.dataGridView1.AutoGenerateColumns = true;
                this.dataGridView1.DataSource = dataSet1;
                this.dataGridView1.DataMember ="Pareto";

                dataGridView1.AutoResizeColumns(
                    DataGridViewAutoSizeColumnsMode.AllCells);

            }
            catch (Exception execc)
            {
                MessageBox.Show("Whoops! Seems we couldnt connect to the server!"
                            +" information:

"
+ execc.Message + execc.StackTrace,
                           "Fatal Error", MessageBoxButtons.OK, MessageBoxIcon.Stop);
                }

        }

        private void AutofillAccounts()
        {
            //get customers list and fill combo box on form load.
            try
            {
                string myConn1 ="Server=derp;" +
                               "Database=AutoPart;" +
                               "uid=fake;" +
                               "pwd=faker;" +
                               "Connect Timeout=6000;";
                SqlConnection conn1 = new SqlConnection(myConn1);
                conn1.Open();
                SqlCommand accountFill = new SqlCommand("SELECT keycode FROM dbo.Customer", conn1);

                SqlDataReader readacc = accountFill.ExecuteReader();

                while (readacc.Read())
                {
                    this.accCollection.Items.Add(readacc.GetString(0).ToString());
                }
                conn1.Close();
            }
            catch(Exception exc1)
            {
                MessageBox.Show("Whoops! Seems we couldnt connect to the server!"
                            +" information:

"
+ exc1.Message + exc1.StackTrace,
                           "Fatal Error", MessageBoxButtons.OK, MessageBoxIcon.Stop);
            }
        }
        public void animateIcon()
        {
            // animate
            pictureBox2.Visible = true;  
        }
    }
}

如您所见,我希望在过程调用之前运行动画,然后在调用之后结束动画。

我对线的知识是全新的。我环顾四周,但现在有点困惑。

这是我的错误:

Thrown:"Cross-thread operation not valid: Control 'Form1' accessed from a thread other than the thread it was created on." (System.InvalidOperationException) Exception Message ="Cross-thread operation not valid: Control 'Form1' accessed from a thread other than the thread it was created on.", Exception Type ="System.InvalidOperationException"

我需要一种非常简单的方法来在我的SQL过程读取时执行动画。

有点像picture.visible = true,开始的时候是假的,结束的时候是假的。


如果要执行此操作,则需要调用。

1
2
3
4
5
6
7
8
9
10
11
          private delegate void InvokeDelegate();

          public void DoSomething()
          {
               if (InvokeRequired)
               {
                    Invoke(new InvokeDelegate(DoSomething));
                    return;
               }
               // dosomething
          }

还可以向委托添加变量并使用它们:

1
2
3
4
5
6
7
8
9
10
      private delegate void InvokeDelegate(string text);
      public void DoSomething(string text)
      {
           if (InvokeRequired)
           {
                Invoke(new InvokeDelegate(DoSomething), text);
                return;
           }
           // dosomething with text
      }

希望这有帮助:)。

斯特凡


正如其他人指出的,您不能在单独的线程上执行与UI相关的操作。

如果希望应用程序具有响应性,则应在单独的线程上执行数据操作。

如果只想显示PictureBox控件,则根本不需要额外的线程:

1
2
3
4
pictureBox2.Visible = true;
pictureBox2.Refresh(); // <-- causes the control to be drawn immediately
...large operation...
pictureBox2.Visible = false;

但是,如果用户(例如alt)来回切换选项卡,或者在您的窗口上拖动另一个窗口,那么当UI线程忙于执行数据操作时,应用程序似乎会挂起。

我很惊讶,这么多人建议您保留当前代码并使用InvokeRequiredInvoke,即使Invoke只在UI线程有时间处理它时执行(在数据操作之后)。


您需要使用invokeRequired从窗体的主线程以外的线程访问/修改控件。文档位于:http://msdn.microsoft.com/en-us/library/system.windows.forms.control.invokeRequired.aspx


你试过Task的吗????

我做了一个简单的测试来展示我将如何制作类似的东西(在wpf中):

XAML:

1
2
3
4
5
6
7
8
9
<Window x:Class="TaskLoading.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="90,33,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
        <Image Height="118" HorizontalAlignment="Left" Margin="90,80,0,0" Name="imgLoading" Stretch="Fill" VerticalAlignment="Top" Width="122" Visibility="Hidden"  Source="/TaskLoading;component/loader_big.gif" />
    </Grid>
</Window>

背后的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Threading;
using System.Threading.Tasks;

namespace TaskLoading
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        public void bigProcess(){
            Thread.Sleep(5000);
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            imgLoading.Visibility = Visibility.Visible; //Make the icon visible.

            /* Start the bigProcess in a background thread: */
            Task changeIcon = Task.Factory.StartNew(() =>
                {
                    bigProcess();
                });

            /* At the end of the process make invisible the icon */
            changeIcon.ContinueWith((r) =>
            {
                imgLoading.Visibility = Visibility.Hidden;

            },
                TaskScheduler.FromCurrentSynchronizationContext()
            );
        }
    }
}