ฉันจะเรียกใช้รหัสง่ายๆในเธรดใหม่ได้อย่างไร


340

ฉันมีรหัสเล็กน้อยที่ฉันต้องการเรียกใช้ในเธรดที่แตกต่างจาก GUI เนื่องจากมันทำให้ฟอร์มหยุดทำงานในขณะที่โค้ดทำงาน (10 วินาทีหรือมากกว่านั้น)

สมมติว่าฉันไม่เคยสร้างเธรดใหม่มาก่อน ตัวอย่างง่ายๆ / พื้นฐานของวิธีทำสิ่งนี้ใน C # และการใช้. NET Framework 2.0 หรือใหม่กว่าคืออะไร


2
คำตอบส่วนใหญ่ที่นี่ในเวลานั้นดี แต่การปรับปรุงใน. NET Framework 4.0 ทำให้สิ่งต่าง ๆ ง่ายขึ้น คุณสามารถใช้วิธี Task.Run () ตามที่อธิบายไว้ในคำตอบนี้: stackoverflow.com/a/31778592/1633949
Richard II

คำตอบ:


336

สถานที่ที่ดีในการเริ่มอ่านคือโจอัลบาฮาริ

หากคุณต้องการสร้างเธรดของคุณเองนี่เป็นเรื่องง่ายอย่างที่มันจะได้รับ:

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

@EdPower สิ่งนี้ใช้ได้เฉพาะกับ Winforms .. หรือใช้งานได้ในเว็บฟอร์ม ..
เมธอดMan

@MethodMan - ใช่มันจะทำงานในเว็บฟอร์ม เริ่มที่นี่:
Ed Power

9
ระวังการตั้งค่าIsBackgroundเป็นจริง มันอาจไม่ได้ทำในสิ่งที่คุณคิดว่ามันทำ สิ่งที่ทำคือกำหนดค่าว่าเธรดจะถูกฆ่าหรือไม่เมื่อเธรดด้านหน้าเบื้องหน้าทั้งหมดตายหรือไม่หรือว่าเธรดจะทำให้แอปพลิเคชันยังมีชีวิตอยู่ หากคุณไม่ต้องการให้เธรดยกเลิกการทำงานกลางคันอย่าตั้งค่าIsBackgroundเป็นจริง
Zero3

10
@EdPower ฉันคิดว่าคุณควรระวังด้วยวิธีใดวิธีหนึ่ง! ตัวอย่างหนึ่งของงานที่คุณอาจไม่ต้องการยุติการทำงานกลางคันคืองานที่บันทึกข้อมูลลงดิสก์ แต่ถ้างานของคุณเหมาะสำหรับการเลิกจ้างในเวลาใดก็ได้ ประเด็นของฉันคือคนที่ต้องระวังเกี่ยวกับการใช้ธงเนื่องจากคุณไม่ได้อธิบายวัตถุประสงค์และการตั้งชื่อสามารถทำให้คนเชื่อว่ามันทำอย่างอื่นมากกว่าสิ่งที่มันทำจริง
Zero3

3
ด้วย. NET Framework 4.0+ เพียงใช้ Task.Run () ดังที่อธิบายไว้ในคำตอบนี้: stackoverflow.com/a/31778592/1633949
Richard II

193

BackgroundWorker ดูเหมือนจะเป็นทางเลือกที่ดีที่สุดสำหรับคุณ

นี่คือตัวอย่างเล็กน้อยของฉัน หลังจากที่คุณคลิกที่ปุ่มผู้ทำงานเบื้องหลังจะเริ่มทำงานในเธรดพื้นหลังและรายงานความคืบหน้าพร้อมกัน นอกจากนี้ยังจะรายงานหลังจากงานเสร็จสมบูรณ์

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

บันทึก:

  • ฉันใส่ทุกอย่างในวิธีการเดียวโดยใช้วิธีที่ไม่ระบุชื่อของ C # เพื่อความเรียบง่าย แต่คุณสามารถดึงพวกเขาออกไปยังวิธีการต่าง ๆ
  • มีความปลอดภัยในการอัปเดต GUI ภายใน ProgressChangedหรือ RunWorkerCompletedตัวจัดการ อย่างไรก็ตามการปรับปรุง GUI จากจะทำให้เกิดDoWork InvalidOperationException

27
ใช้ System.ComponentModel; (อาจช่วยให้ผู้คนไม่ทำการค้นหาด้วย Google ขอบคุณสำหรับตัวอย่างโค้ดที่มีประโยชน์นี้) +1
sooprise

@Gant ขอบคุณสำหรับรหัสตัวอย่าง เมื่อฉันลองใช้รหัสของคุณเหตุการณ์ ProgressChanged ก็ไม่เริ่มทำงาน อย่างไรก็ตามเมื่อฉันลองตอบคำถามที่คล้ายกันที่นี่ [ stackoverflow.com/questions/23112676/…มันทำงานได้
Martin

1
@sooprise Ctrl + ช่วยคุณในสถานการณ์เหล่านี้! (สมมติว่าคุณกำลังใช้ Visual Studio)
SepehrM

100

ThreadPool.QueueUserWorkItemเหมาะสวยสำหรับสิ่งที่ง่าย ข้อแม้เท่านั้นที่เข้าถึงตัวควบคุมจากเธรดอื่น

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

อีกอย่างที่ฉันชอบ หนึ่งบรรทัดอย่างรวดเร็วสำหรับทางแยก ฉันมีปุ่มโทรด่วน (ตัวอย่าง) ทำงานได้ดีมากกับการควบคุม คุณเพียงแค่ต้องรู้วิธีการใช้วิงวอนและInvokeRequired
Bitterblue

1
+1 โปรดทราบว่าผู้รับมอบสิทธิ์ช่วยให้คุณสามารถห่อพารามิเตอร์ได้อย่างเรียบร้อยเช่นกัน
Will Bickford

วิธีใช้ ThreadPool.QueueUserWorkItem พร้อมฟังก์ชันการโทรกลับหมายถึงเมื่องานเสร็จแล้วโทรกลับจะแจ้งให้เราทราบ
Mou

76

รวดเร็วและสกปรก แต่มันจะทำงาน:

ใช้ที่ด้านบน:

using System.Threading;

รหัสง่าย ๆ :

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

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

ฉันเพิ่งโยนมันลงในแอปพลิเคชั่นคอนโซลใหม่สำหรับ exmaple


8
โปรดจำไว้ว่าเธรดที่ตั้งค่าสถานะ IsBackground จะไม่สิ้นสุดโดยอัตโนมัติโดยรันไทม์ แอปนี้ต้องการการจัดการเธรดโดยแอป ถ้าคุณปล่อยให้เธรดที่ทำเครื่องหมายว่าไม่ใช่พื้นหลังหลังจากดำเนินการเธรดเธรดจะถูกยกเลิกสำหรับคุณ
CmdrTallen

14
@CrdrTallen: ไม่ถูกต้องนัก เธรดที่มีชื่อว่า IsBackground = true หมายความว่าเธรดจะไม่หยุดกระบวนการออกจากกระบวนการเช่นกระบวนการจะออกจากเมื่อเธรดทั้งหมดที่มี IsBackground = false ได้ออกแล้ว
Phil Devaney

66

นี่คือตัวเลือกอื่น:

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

1
ถ้าคุณสร้างเธรดด้วยวิธีนี้คุณจะหยุดเธรดนี้จากเธรด UI ได้อย่างไร
slayernoah

1
@slayernoah คุณสามารถส่ง CancellationTokenSource เป็นพารามิเตอร์และตั้งค่าโทเค็นการยกเลิกนอกเธรด: msdn.microsoft.com/en-us/library/dd997364(v=vs.110).aspx
Spongebob Comrade

7
รหัสนี้จะไม่รับประกันว่าคุณจะมีกระทู้ใหม่ การตัดสินใจขึ้นอยู่กับการนำไปใช้งานปัจจุบันของ ThreadPool ที่คุณกำลังใช้ ดูตัวอย่างstackoverflow.com/questions/13570579/…
Giulio Caccin

3
@GiulioCaccin เป็นไปได้ที่ ThreadPool จะเลือกเธรดที่มีอยู่จากกลุ่มของมัน แต่แน่นอนว่าเป็นเธรดอื่น
Spongebob สหาย

40

ลองใช้คลาสBackgroundWorker คุณให้ผู้ได้รับมอบหมายสิ่งที่จะทำงานและได้รับแจ้งเมื่องานเสร็จ มีตัวอย่างในหน้า MSDN ที่ฉันเชื่อมโยงอยู่


6
คุณสามารถทำสิ่งนี้ได้ในคลาสของเธรด แต่ BackgroundWorker จะให้วิธีการสำหรับการทำเธรดให้เสร็จและรายงานความคืบหน้าซึ่งคุณต้องหาวิธีใช้ด้วยตัวคุณเอง อย่าลืมว่าคุณต้องใช้ Invoke เพื่อพูดคุยกับ UI!
Robert Rossney

1
ได้รับการเตือน: BackgroundWorker มีข้อ จำกัด เล็กน้อย แต่สำหรับคนทั่วไป "ฉันต้องการที่จะออกไปและทำบางสิ่งบางอย่าง
Merus

10
@ Merus คุณสามารถขยายขอบเขตข้อ จำกัด ที่ลึกซึ้งเหล่านั้นได้อย่างไร
Christian Hudon

14

หากคุณต้องการรับค่า:

var someValue;

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

thread.Start();

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

6

ใส่รหัสนั้นในฟังก์ชั่น (รหัสที่ไม่สามารถเรียกใช้งานบนเธรดเดียวกับ GUI) และเพื่อทริกเกอร์การเรียกใช้โค้ดนั้นมีดังต่อไปนี้

Thread myThread= new Thread(nameOfFunction);

workerThread.Start();

การเรียกใช้ฟังก์ชันเริ่มต้นบนวัตถุเธรดจะทำให้การเรียกใช้ฟังก์ชันของคุณในเธรดใหม่


3
@ user50612 คุณกำลังพูดถึงอะไร เธรดใหม่จะทำงานnameOfFunctionในพื้นหลังและไม่อยู่ในเธรด GUI ปัจจุบัน IsBackgroundคุณสมบัติกำหนดว่าด้ายจะทำให้แอพลิเคชันที่มีชีวิตอยู่หรือไม่: msdn.microsoft.com/en-us/library/...
zero3

5

ต่อไปนี้เป็นวิธีที่สามารถใช้เธรดที่มี progressBar ได้เพียงเพื่อเน้นวิธีการทำงานของเธรดในรูปแบบที่มีสาม progressBar และปุ่ม 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 รหัสของคุณ (ด้านบน) ไม่ถูกต้อง คุณไม่จำเป็นต้องหมุนรอจนเสร็จ EndInvoke จะบล็อกจนกว่าจะส่งสัญญาณ WaitHandle

หากคุณต้องการบล็อกจนกว่าจะเสร็จสมบูรณ์คุณก็ต้อง

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

หรืออีกวิธีหนึ่ง

ar.AsyncWaitHandle.WaitOne();

แต่สิ่งที่เป็นจุดของการออกสายใด ๆ ถ้าคุณบล็อก? คุณอาจใช้สายซิงโครนัสก็ได้เช่นกัน ทางออกที่ดีกว่าคือการไม่ปิดกั้นและผ่านเข้าไปในแลมบ์ดาเพื่อทำความสะอาด:

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

สิ่งหนึ่งที่ต้องจำไว้คือคุณต้องโทรหา EndInvoke ผู้คนจำนวนมากลืมสิ่งนี้และจบลงด้วยการรั่วไหลของ WaitHandle เนื่องจากการใช้งาน async ส่วนใหญ่จะปล่อย waithandle ใน EndInvoke


2

ถ้าคุณจะใช้วัตถุ Raw Thread คุณต้องตั้งค่า IsBackground ให้เป็นจริงอย่างน้อยที่สุดและคุณควรตั้งค่ารุ่น Threading Apartment (อาจเป็น 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()
}

ฉันจะแนะนำคลาส BackgroundWorker ถ้าคุณต้องการการโต้ตอบกับ UI


ระวังเกี่ยวกับการตั้งค่า IsBackground เป็นจริง มันอาจไม่ได้ทำในสิ่งที่คุณคิดว่ามันทำ สิ่งที่ทำคือกำหนดค่าว่าเธรดจะถูกฆ่าเมื่อเธรดพื้นหน้าทั้งหมดตายหรือไม่หรือเธรดจะทำให้แอปพลิเคชันยังมีชีวิตอยู่ หากคุณไม่ต้องการให้เธรดยุติการทำงานกลางคันอย่าตั้งค่า IsBackground เป็นจริง
Zero3


1

ตัวเลือกอื่นที่ใช้ผู้รับมอบสิทธิ์และกลุ่มเธรด ...

สมมติว่า 'GetEnergyUsage' เป็นวิธีการที่ใช้ DateTime และ DateTime อื่นเป็นอาร์กิวเมนต์อินพุตและส่งคืน Int ...

// 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
ชาร์ลส์รหัสของคุณแตกต่างจากการใช้งานจริงint usageCnt = nrgDel.Invoke(lastRunTime, procDT, null, null);หรือไม่? ดูเหมือนว่ามันระงับเธรดปัจจุบันกับการนอนหลับอยู่แล้ว ... ฉันคิดว่ามันจะช่วยให้อะไรกับ GUI แช่แข็งถ้าคุณเรียกมันในหัวข้อ GUI
IgorK

ใช่ BeginInvoke โทรไปยังผู้รับมอบสิทธิ์ในเธรดอื่นจากกลุ่มเธรด ... ถ้าคุณใช้ Invoke ผู้รับมอบสิทธิ์จะเรียกว่า syncronously บนเธรดปัจจุบัน ... แต่คุณพูดถูกการนอนหลับผิดคุณควรกำจัดมันและ รวบรวมผลลัพธ์โดยใช้ฟังก์ชั่นการโทรกลับ ฉันจะแก้ไขเพื่อแสดงว่า
Charles Bretana

1

มีหลายวิธีในการรันเธรดแยกกันใน. Net แต่ละรายการมีพฤติกรรมที่แตกต่างกัน คุณต้องการเรียกใช้เธรดต่อหลังจาก GUI หยุดทำงานหรือไม่ คุณต้องการส่งผ่านข้อมูลระหว่างเธรดและ GUI หรือไม่ เธรดจำเป็นต้องอัพเดต GUI หรือไม่? เธรดควรทำงานอย่างใดอย่างหนึ่งแล้วออกจากหรือควรจะทำงานต่อไป? คำตอบสำหรับคำถามเหล่านี้จะบอกวิธีการใช้

มีบทความวิธีการ asyncที่ดีที่เว็บไซต์โครงการรหัสที่อธิบายวิธีการต่าง ๆ และให้รหัสตัวอย่าง

หมายเหตุบทความนี้เขียนขึ้นก่อนรูปแบบasync / awaitและTask Parallel Libraryถูกนำมาใช้ใน. NET


นี่คือประเภทของข้อมูลที่ฉันกำลังมองหา แต่คำตอบของคุณเป็นเหยื่อของลิงค์เน่า
Chris

ขอบคุณที่แจ้งให้เราทราบ @Chris; ลิงก์ถูกแก้ไขแล้ว
Dour High Arch

0

วิธีการ: ใช้เธรดพื้นหลังเพื่อค้นหาไฟล์

คุณจะต้องระมัดระวังอย่างมากกับการเข้าถึงจากเธรดอื่น ๆ ไปยังสิ่งเฉพาะของ GUI (เป็นเรื่องปกติสำหรับชุดเครื่องมือ GUI จำนวนมาก) หากคุณต้องการอัปเดตบางอย่างใน GUI จากการประมวลผลเธรดให้ตรวจสอบคำตอบที่ฉันคิดว่ามีประโยชน์สำหรับ WinForms สำหรับ WPF ดูสิ่งนี้ (มันแสดงให้เห็นถึงวิธีการสัมผัสองค์ประกอบใน UpdateProgress () วิธีการดังนั้นมันจะทำงานจากกระทู้อื่น ๆ แต่ที่จริงฉันไม่ชอบมันไม่ได้ทำCheckAccess()ก่อนที่จะทำBeginInvokeผ่าน Dispathcer ดูและค้นหา CheckAccess ในนั้น

กำลังมองหาหนังสือเฉพาะ. NET ในเธรดและพบหนังสือเล่มนี้ (ดาวน์โหลดได้ฟรี) ดูhttp://www.albahari.com/threading/สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับเรื่องนี้

ฉันเชื่อว่าคุณจะพบสิ่งที่คุณต้องเปิดใช้งานการดำเนินการในฐานะเธรดใหม่ใน 20 หน้าแรกและมีอีกมากมาย (ไม่แน่ใจเกี่ยวกับตัวอย่างโค้ด GUI เฉพาะที่ฉันหมายถึงเฉพาะเธรดอย่างเคร่งครัด) ยินดีที่จะได้ยินสิ่งที่ชุมชนคิดเกี่ยวกับงานนี้เพราะฉันอ่านข้อความนี้ ตอนนี้ดูเรียบร้อยสำหรับฉัน (สำหรับการแสดง. NET วิธีการเฉพาะและประเภทสำหรับเธรด) นอกจากนี้ยังครอบคลุม. NET 2.0 (ไม่ใช่โบราณ 1.1) สิ่งที่ฉันชื่นชมจริงๆ


0

ฉันขอแนะนำให้ดูที่Power Threading Libraryของ Jeff Richter และโดยเฉพาะ IAsyncEnumerator ดูวิดีโอในบล็อกของ Charlie Calvertที่ Richter ดำเนินการเพื่อดูภาพรวมที่ดี

อย่านำออกโดยชื่อเพราะมันทำให้รหัสการเขียนโปรแกรมแบบอะซิงโครนัสง่ายขึ้น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.