Làm thế nào để tôi chạy một đoạn mã đơn giản trong một luồng mới?


340

Tôi có một chút mã mà tôi cần chạy trong một luồng khác với GUI vì hiện tại nó làm cho biểu mẫu bị đóng băng trong khi mã chạy (10 giây hoặc lâu hơn).

Giả sử tôi chưa bao giờ tạo ra một chủ đề mới trước đây; ví dụ đơn giản / cơ bản về cách thực hiện điều này trong C # và sử dụng .NET Framework 2.0 trở lên?


2
Hầu hết các câu trả lời ở đây đều tốt vào thời điểm đó, nhưng những cải tiến trong .NET Framework 4.0 giúp mọi thứ đơn giản hơn. Bạn có thể sử dụng phương thức Task.Run (), như được mô tả trong câu trả lời này: stackoverflow.com/a/31778592/1633949
Richard II

Câu trả lời:


336

Nơi tốt để bắt đầu đọc là Joe Albahari .

Nếu bạn muốn tạo chủ đề của riêng mình, điều này đơn giản như nó được:

using System.Threading;
new Thread(() => 
{
    Thread.CurrentThread.IsBackground = true; 
    /* run your code here */ 
    Console.WriteLine("Hello, world"); 
}).Start();

@EdPower, điều này chỉ áp dụng cho Winforms .. hay nó sẽ hoạt động trong Biểu mẫu web ..?
Phương thức

@MethodMan - Có, nó sẽ hoạt động trong Biểu mẫu web. Bắt đầu tại đây:
Ed Power

9
Hãy cẩn thận về việc thiết lập IsBackgroundthành đúng. Nó có thể không làm những gì bạn nghĩ nó làm. Những gì nó làm, là cấu hình xem luồng sẽ bị giết khi tất cả các luồng tiền cảnh đã chết, hoặc liệu luồng sẽ giữ cho ứng dụng tồn tại. Nếu bạn không muốn chủ đề của mình bị chấm dứt giữa lúc thực thi, đừng đặt IsBackgroundthành đúng.
Zero3

10
@EdPower Tôi nghĩ bạn cũng nên cẩn thận! Một ví dụ về một nhiệm vụ mà bạn có thể không muốn chấm dứt thực thi giữa chừng là một nhiệm vụ lưu dữ liệu vào đĩa. Nhưng chắc chắn, nếu nhiệm vụ của bạn phù hợp để chấm dứt bất cứ lúc nào, cờ vẫn ổn. Quan điểm của tôi chỉ là người ta cần cẩn thận khi sử dụng cờ , vì bạn không mô tả mục đích của nó và việc đặt tên của nó có thể dễ dàng khiến người ta tin rằng nó làm một cái gì đó khác với những gì nó thực sự làm.
Zero3

3
với .NET Framework 4.0+, chỉ cần sử dụng Task.Run (), như được mô tả trong câu trả lời này: stackoverflow.com/a/31778592/1633949
Richard II

193

BackgroundWorker dường như là sự lựa chọn tốt nhất cho bạn

Đây là ví dụ tối thiểu của tôi. Sau khi bạn nhấp vào nút, nhân viên nền sẽ bắt đầu làm việc trong luồng nền và đồng thời báo cáo tiến trình của nó. Nó cũng sẽ báo cáo sau khi công việc hoàn thành.

using System.ComponentModel;
...
    private void button1_Click(object sender, EventArgs e)
    {
        BackgroundWorker bw = new BackgroundWorker();

        // this allows our worker to report progress during work
        bw.WorkerReportsProgress = true;

        // what to do in the background thread
        bw.DoWork += new DoWorkEventHandler(
        delegate(object o, DoWorkEventArgs args)
        {
            BackgroundWorker b = o as BackgroundWorker;

            // do some simple processing for 10 seconds
            for (int i = 1; i <= 10; i++)
            {
                // report the progress in percent
                b.ReportProgress(i * 10);
                Thread.Sleep(1000);
            }

        });

        // what to do when progress changed (update the progress bar for example)
        bw.ProgressChanged += new ProgressChangedEventHandler(
        delegate(object o, ProgressChangedEventArgs args)
        {
            label1.Text = string.Format("{0}% Completed", args.ProgressPercentage);
        });

        // what to do when worker completes its task (notify the user)
        bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(
        delegate(object o, RunWorkerCompletedEventArgs args)
        {
            label1.Text = "Finished!";
        });

        bw.RunWorkerAsync();
    }

Ghi chú:

  • Tôi đặt mọi thứ trong một phương thức bằng cách sử dụng phương thức ẩn danh của C # để đơn giản nhưng bạn luôn có thể kéo chúng ra các phương thức khác nhau.
  • Nó là an toàn để cập nhật GUI trong ProgressChangedhoặc RunWorkerCompletedxử lý. Tuy nhiên, cập nhật GUI từ DoWork sẽ gây ra InvalidOperationException.

27
Sử dụng System.ComponentModel; (có thể cứu mọi người khỏi thực hiện tìm kiếm google, cảm ơn vì ví dụ mã hữu ích này) +1
soopawn

@Gant Cảm ơn bạn đã mã mẫu. Khi tôi thử mã của bạn, sự kiện ProgressChanged không kích hoạt. Tuy nhiên, khi tôi thử câu trả lời được chấp nhận tương tự ở đây [ stackoverflow.com/questions/23112676/ thì nó đã hoạt động.
Martin

1
@soopawn Ctrl +. giúp bạn trong những tình huống này! (Giả sử bạn đang sử dụng Visual Studio)
SepehrM

100

Các ThreadPool.QueueUserWorkItem là lý tưởng khá cho một cái gì đó đơn giản. Thông báo trước duy nhất là truy cập một điều khiển từ luồng khác.

System.Threading.ThreadPool.QueueUserWorkItem(delegate {
    DoSomethingThatDoesntInvolveAControl();
}, null);

Cũng là yêu thích của tôi. Một dòng nhanh chóng cho ngã ba . Tôi có nó trên quay số nhanh (đoạn trích). Hoạt động rất tốt với Điều khiển. Bạn chỉ cần biết cách sử dụng InvokeInvokeRequired .
Bitterblue

1
+1 Lưu ý rằng đại biểu cũng cho phép bạn bọc các tham số gọn gàng.
Will Bickford

Cách sử dụng ThreadPool.QueueUserWorkItem với chức năng gọi lại nghĩa là khi hoàn thành công việc thì gọi lại sẽ thông báo cho chúng tôi.
Mou

76

Nhanh và bẩn, nhưng nó sẽ hoạt động:

Sử dụng ở trên cùng:

using System.Threading;

mã đơn giản:

static void Main( string[] args )
{
    Thread t = new Thread( NewThread );
    t.Start();
}

static void NewThread()
{
    //code goes here
}

Tôi vừa ném cái này vào một ứng dụng console mới cho một exmaple


8
Hãy nhớ các chủ đề được gắn cờ IsBackground không được chấm dứt tự động bởi Runtime. Điều này đòi hỏi quản lý luồng bởi ứng dụng. Nếu bạn để lại chủ đề được đánh dấu là không nền thì sau khi thực hiện các chủ đề, chủ đề được chấm dứt cho bạn.
CmdrTallen

14
@CmdrTallen: Điều đó không hoàn toàn đúng. Một chuỗi được đánh dấu IsBackground = true có nghĩa là luồng đó sẽ không dừng quá trình thoát, tức là một quá trình sẽ thoát khi tất cả các luồng có IsBackground = false đã thoát
Phil Devaney

66

Đây là một lựa chọn khác:

Task.Run(()=>{
//Here is a new thread
});

1
Nếu bạn tạo một luồng theo cách này, làm thế nào bạn có thể dừng luồng này khỏi luồng UI?
slayernoah

1
@slayernoah bạn có thể vượt qua một CancellationTokenSource như một tham số cho nó và thiết lập việc hủy bỏ thẻ bên ngoài các chủ đề: msdn.microsoft.com/en-us/library/dd997364(v=vs.110).aspx
Spongebob đồng chí

7
Mã này sẽ không đảm bảo cho bạn có một chủ đề mới. Quyết định tùy thuộc vào việc triển khai ThreadPool hiện tại mà bạn đang sử dụng. Xem ví dụ stackoverflow.com/questions/13570579/
Mạnh

3
@GiulioCaccin Có thể ThreadPool chọn một luồng hiện có từ nhóm của nó nhưng nó chắc chắn là một luồng khác.
Đồng chí

40

Hãy thử sử dụng lớp BackgroundWorker . Bạn cung cấp cho nó đại biểu cho những gì để chạy, và được thông báo khi công việc kết thúc. Có một ví dụ trên trang MSDN mà tôi đã liên kết đến.


6
Bạn chắc chắn có thể làm điều này với lớp Thread, nhưng BackgroundWorker cung cấp cho bạn các phương thức để hoàn thành luồng và báo cáo tiến trình mà nếu không bạn phải tự tìm ra cách sử dụng. Đừng quên rằng bạn phải sử dụng Gọi để nói chuyện với UI!
Robert Rossney

1
Được cảnh báo: BackgroundWorker có một số hạn chế tinh tế, nhưng với điểm chung "Tôi muốn đi xa và làm một cái gì đó và để hình thức của tôi phản ứng nhanh" thật tuyệt vời.
Merus

10
@Merus, bạn có thể mở rộng về những hạn chế tinh tế đó là gì không?
Christian Hudon

14

Nếu bạn muốn nhận được một giá trị:

var someValue;

Thread thread = new Thread(delegate()
            {                 
                //Do somthing and set your value
                someValue = "Hello World";
            });

thread.Start();

while (thread.IsAlive)
  Application.DoEvents();

6

Đặt mã đó vào một hàm (mã không thể được thực thi trên cùng một luồng với GUI) và để kích hoạt thực thi mã đó, hãy đặt như sau.

Thread myThread= new Thread(nameOfFunction);

workerThread.Start();

Gọi hàm start trên đối tượng luồng sẽ gây ra việc thực hiện lệnh gọi hàm của bạn trong luồng mới.


3
@ user50612 Bạn đang nói về cái gì? Chuỗi mới sẽ chạy nameOfFunctiontrong nền chứ không phải trên luồng GUI hiện tại. Các IsBackgroundbất động sản sẽ xác định xem các chủ đề sẽ tiếp tục ứng dụng còn sống hay không: msdn.microsoft.com/en-us/library/...
Zero3

5

Ở đây làm thế nào có thể sử dụng các luồng với một ProgressBar, nó chỉ để hiểu cách thức các luồng hoạt động, trong biểu mẫu có ba nút ProgressBar và 4:

 public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }
    Thread t, t2, t3;
    private void Form1_Load(object sender, EventArgs e)
    {

        CheckForIllegalCrossThreadCalls = false;

         t = new Thread(birinicBar); //evry thread workes with a new progressBar


         t2 = new Thread(ikinciBar);


         t3 = new Thread(ucuncuBar);

    }

    public void birinicBar() //to make progressBar work
    {
        for (int i = 0; i < 100; i++) {
            progressBar1.Value++;
            Thread.Sleep(100); // this progressBar gonna work faster
        }
    }

    public void ikinciBar()
    {
        for (int i = 0; i < 100; i++)
        {
            progressBar2.Value++;
            Thread.Sleep(200);
        }


    }

    public void ucuncuBar()
    {
        for (int i = 0; i < 100; i++)
        {
            progressBar3.Value++;
            Thread.Sleep(300);
        }
    }

    private void button1_Click(object sender, EventArgs e) //that button to start the threads
    {
        t.Start();
        t2.Start(); t3.Start();

    }

    private void button4_Click(object sender, EventArgs e)//that button to stup the threads with the progressBar
    {
        t.Suspend();
        t2.Suspend();
        t3.Suspend();
    }

    private void button2_Click(object sender, EventArgs e)// that is for contuniue after stuping
    {
        t.Resume();
        t2.Resume();
        t3.Resume();
    }

    private void button3_Click(object sender, EventArgs e) // finally with that button you can remove all of the threads
    {
        t.Abort();
        t2.Abort();
        t3.Abort();
    }
}

3
// following declaration of delegate ,,,
public delegate long GetEnergyUsageDelegate(DateTime lastRunTime, 
                                            DateTime procDateTime);

// following inside of some client method
GetEnergyUsageDelegate nrgDel = GetEnergyUsage;
IAsyncResult aR = nrgDel.BeginInvoke(lastRunTime, procDT, null, null);
while (!aR.IsCompleted) Thread.Sleep(500);
int usageCnt = nrgDel.EndInvoke(aR);

Charles mã của bạn (ở trên) là không chính xác. Bạn không cần phải quay chờ đợi để hoàn thành. EndInvoke sẽ chặn cho đến khi WaitHandle được báo hiệu.

Nếu bạn muốn chặn cho đến khi hoàn thành, bạn chỉ cần

nrgDel.EndInvoke(nrgDel.BeginInvoke(lastRuntime,procDT,null,null));

Hay cách khác

ar.AsyncWaitHandle.WaitOne();

Nhưng điểm phát hành cuộc gọi anyc là gì nếu bạn chặn? Bạn cũng có thể chỉ cần sử dụng một cuộc gọi đồng bộ. Đặt cược tốt hơn là không chặn và vượt qua trong lambda để dọn dẹp:

nrgDel.BeginInvoke(lastRuntime,procDT,(ar)=> {ar.EndInvoke(ar);},null);

Một điều cần lưu ý là bạn phải gọi EndInvoke. Rất nhiều người quên điều này và cuối cùng đã rò rỉ WaitHandle vì hầu hết các triển khai async đều phát hành waithandle trong EndInvoke.


2

Nếu bạn định sử dụng đối tượng Thread thô thì bạn cần đặt IsBackground ở mức tối thiểu và bạn cũng nên đặt mô hình Căn hộ luồng (có thể là STA).

public static void DoWork()
{
    // do some work
}

public static void StartWorker()
{
    Thread worker = new Thread(DoWork);
    worker.IsBackground = true;
    worker.SetApartmentState(System.Threading.ApartmentState.STA);
    worker.Start()
}

Tôi muốn giới thiệu lớp BackgroundWorker nếu bạn cần tương tác UI.


Hãy cẩn thận về việc đặt IsBackground thành true. Nó có thể không làm những gì bạn nghĩ nó làm. Những gì nó làm, là cấu hình xem luồng sẽ bị giết khi tất cả các luồng tiền cảnh đã chết, hoặc liệu luồng sẽ giữ cho ứng dụng tồn tại. Nếu bạn không muốn luồng kết thúc thực thi giữa chừng, đừng đặt IsBackground thành true.
Zero3


1

một tùy chọn khác, sử dụng các đại biểu và Nhóm luồng ...

giả sử 'GetEnergyUsage' là phương thức lấy DateTime và một DateTime khác làm đối số đầu vào và trả về một ...

// following declaration of delegate ,,,
public delegate long GetEnergyUsageDelegate(DateTime lastRunTime, 
                                            DateTime procDateTime);

// following inside of some client method 
GetEnergyUsageDelegate nrgDel = GetEnergyUsage;                     
IAsyncResult aR = nrgDel.BeginInvoke(lastRunTime, procDT, null, null);
while (!aR.IsCompleted) Thread.Sleep(500);
int usageCnt = nrgDel.EndInvoke(aR);

1
Charles, mã của bạn có chức năng khác với int usageCnt = nrgDel.Invoke(lastRunTime, procDT, null, null);? Dường như nó tạm dừng chủ đề hiện tại với chế độ ngủ ... Tôi nghĩ rằng nó sẽ chẳng giúp ích gì với việc đóng băng GUI nếu bạn gọi nó trong chủ đề GUI
IgorK

Có, BeginInvoke gọi đại biểu trên một luồng khác từ nhóm luồng ... Nếu bạn sử dụng Gọi, đại biểu được gọi đồng bộ trên luồng hiện tại ... Nhưng bạn đã đúng, giấc ngủ bị sai ... bạn nên loại bỏ điều đó và thu thập các kết quả bằng cách sử dụng chức năng gọi lại. Tôi sẽ chỉnh sửa để cho thấy điều đó
Charles Bretana

1

Có nhiều cách để chạy các luồng riêng biệt trong .Net, mỗi cách có các hành vi khác nhau. Bạn có cần tiếp tục chạy chuỗi sau khi thoát GUI không? Bạn có cần truyền thông tin giữa luồng và GUI không? Chủ đề có cần cập nhật GUI không? Chủ đề nên làm một nhiệm vụ sau đó thoát, hoặc nó nên tiếp tục chạy? Các câu trả lời cho những câu hỏi này sẽ cho bạn biết nên sử dụng phương pháp nào.

Có một bài viết phương pháp async tốt tại trang web Code Project mô tả các phương thức khác nhau và cung cấp mã mẫu.

Lưu ý bài viết này được viết trước khi mẫu async / awaitThư viện song song tác vụ được đưa vào .NET.


Đây là loại thông tin tôi đang tìm kiếm, nhưng câu trả lời của bạn là nạn nhân của mục đích liên kết.
Chris

Cảm ơn đã cho tôi biết, @Chris; liên kết cố định.
Dour High Arch

0

Cách làm: Sử dụng Chủ đề nền để Tìm kiếm tệp

Bạn phải rất cẩn thận với quyền truy cập từ các luồng khác vào các công cụ cụ thể của GUI (điều này phổ biến đối với nhiều bộ công cụ GUI). Nếu bạn muốn cập nhật một cái gì đó trong GUI từ xử lý luồng, hãy kiểm tra câu trả lời này mà tôi nghĩ là hữu ích cho WinForms. Đối với WPF, hãy xem điều này (nó cho thấy cách chạm vào thành phần trong phương thức UpdateProTHER () để nó sẽ hoạt động từ các luồng khác, nhưng thực sự tôi không thích nó không hoạt động CheckAccess()trước khi thực hiện BeginInvokethông qua Dispathcer, hãy xem và tìm kiếm CheckAccess trong nó)

Đã tìm cuốn sách cụ thể .NET về luồng và tìm thấy cuốn sách này (có thể tải xuống miễn phí). Xem http://www.albahari.com/threading/ để biết thêm chi tiết về nó.

Tôi tin rằng bạn sẽ tìm thấy những gì bạn cần để khởi chạy thực thi dưới dạng luồng mới trong 20 trang đầu tiên và nó có nhiều hơn nữa (không chắc chắn về đoạn mã cụ thể GUI tôi có nghĩa là đặc biệt nghiêm ngặt đối với luồng). Sẽ rất vui khi nghe cộng đồng nghĩ gì về tác phẩm này vì tôi đang đọc bài này. Bây giờ trông khá gọn gàng đối với tôi (để hiển thị các phương thức và kiểu cụ thể của .NET cho luồng). Ngoài ra, nó bao gồm .NET 2.0 (và không phải 1.1 cổ) những gì tôi thực sự đánh giá cao.


0

Tôi khuyên bạn nên xem Thư viện luồng điện của Jeff Richter và cụ thể là IAsyncEnumerator. Hãy xem video trên blog của Charlie Calvert, nơi Richter xem qua nó để có cái nhìn tổng quan.

Đừng bỏ qua tên này vì nó làm cho các tác vụ lập trình không đồng bộ dễ dàng hơn để viết mã.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.