2013-01-28 24 views
5

Próbuję użyć wątków i zapobiec zamrożeniu programu, gdy wątek jest zajęty. Powinien pokazać postęp (pisanie 0/1), a nie tylko pokazać wynik po jego zakończeniu, zamrożenie formularza w międzyczasie.C# Gwintowanie za pomocą invoke, zamrożenie formularza

W bieżącym programie próbuję napisać do pola tekstowego, a faktycznie widzę stały postęp, a na formularzu nie mogą wpływać zadania drugiego wątku.

Co mam teraz, mogę napisać do pola tekstowego za pomocą wątku za pomocą invoke, ale pokazuje tylko wynik (Formularz zamarza, gdy wątek jest zajęty), a formularz zawiesza się.

Forma obrazu:

enter image description here

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.Threading; 

namespace MultiThreading 
{ 
public partial class MultiThreading : Form 
{ 
    public MultiThreading() 
    { 
     InitializeComponent(); 
    } 

    Thread writeOne, writeTwo; 

    private void writeText(TextBox textBox, string text) 
    { 
     if (textBox.InvokeRequired) 
     { 
      textBox.BeginInvoke((MethodInvoker)delegate() 
      { 
       for (int i = 0; i < 500; i++) 
       { 
        textBox.Text += text; 
       } 
      }); 
     } 
     else 
     { 
      for (int i = 0; i < 500; i++) 
      { 
       textBox.Text += text; 
      } 
     } 
    } 
    private void btnWrite1_Click(object sender, EventArgs e) 
    { 
     writeOne = new Thread(() => writeText(txtOutput1, "0")); 
     writeOne.Start(); 
    } 

    private void btnWrite2_Click(object sender, EventArgs e) 
    { 
     writeTwo = new Thread(() => writeText(txtOutput2, "1")); 
     writeTwo.Start(); 
    } 

    private void btnClear1_Click(object sender, EventArgs e) 
    { 
     txtOutput1.Clear(); 
    } 

    private void btnClear2_Click(object sender, EventArgs e) 
    { 
     txtOutput2.Clear(); 
    } 

    private void btnWriteBoth_Click(object sender, EventArgs e) 
    { 
     writeOne = new Thread(() => writeText(txtOutput1, "0")); 
     writeTwo = new Thread(() => writeText(txtOutput2, "1")); 

     writeOne.Start(); 
     writeTwo.Start(); 
    } 

    private void btnClearBoth_Click(object sender, EventArgs e) 
    { 
     txtOutput1.Clear(); 
     txtOutput2.Clear(); 
    } 
} 

} 

EDIT:

Btw dla każdego, zastanawiając się, jestem nowy na wielowątkowość i po prostu próbuję napisać mały program, aby zrozumieć najlepszy sposób na zrobienie tego.

Rozumiem, że moja poprzednia próba nie pomogła mi, ponieważ nadal nie dawałem formularza okazji do aktualizacji, więc do niego dotarłem.

OK, tak działa 1 wątek w ten sposób działa, ale nadal działa wiele wątków razem, nie zaktualizuje formularza, dopóki wątek nie zostanie ukończony.
Dodałem thread.sleep(), więc mogę spróbować wyczyścić podczas pisania, aby sprawdzić, czy nadal mogę korzystać z formularza.

Podczas pisania do 1 pola tekstowego nadal mogę wyczyścić ekran podczas pisania.
Ale kiedy używam 2 wątków, nie mogę już używać formularza do zakończenia wątku i daje dane wyjściowe.

private void writeText(TextBox textBox, string text) 
    { 
     for (int i = 0; i < 500; i++) 
     { 
      Invoke(new MethodInvoker(() => 
      { 
       textBox.Text += text; 
       Thread.Sleep(2); 
      })); 
     } 

    } 

(Jeśli jestem całkowicie błędne w tej sprawie nie mam nic z odczytaniem przez niektórych przykładach/wątków, ja wciąż staram się zobaczyć, co jest najlepszym sposobem, aby to zrobić, oprócz BackgroundWorker)

EDIT 2:

mam zmniejszyć liczbę wywołuje poprzez zmniejszenie ilości do pisania, ale aby zwiększyć opóźnienie daje ten sam efekt ciągłego pisania, tylko zmniejsza obciążenie.

private void writeText(TextBox textBox, string text) 
    { 
     for (int i = 0; i < 500; i++) 
     { 
      Invoke(new MethodInvoker(() => 
      { 
       textBox.Text += text; 
       Thread.Sleep(2); 
      })); 
     } 

    } 

Edycja 3:

przykład Sumeet za pomocą działa

Application.DoEvents();

(zauważyć s, .DoEvent nie działa, prawdopodobnie literówka: P), pisząc wiele strun jednocześnie & mający im pokazać postęp, a nie tylko wynik.

Więc Kod aktualizować ponownie :)

* Za pomocą nowego przycisku, aby utworzyć 5 wątków, które zapisują liczbę losową do obu pól tekstowych

private void writeText(TextBox textBox, string text) 
    { 
     for (int i = 0; i < 57; i++) 
     { 
      Invoke(new MethodInvoker(() => 
      { 
       textBox.Text += text; 
       Thread.Sleep(5); 
       Application.DoEvents(); 
      })); 
     } 

    } 
private void btnNewThread_Click(object sender, EventArgs e) 
    { 
     Random random = new Random(); 
     int[] randomNumber = new int[5]; 
     for (int i = 0; i < 5; i++) 
     { 
      randomNumber[i] = random.Next(2, 9); 
      new Thread(() => writeText(txtOutput1, randomNumber[i-1].ToString())).Start(); 
      new Thread(() => writeText(txtOutput2, randomNumber[i-1].ToString())).Start(); 
     } 
    } 
+3

Rozważ użycie [ 'BackgroundWorker'] (http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx) jeśli masz długo działa zakonczeniu w tle, a interfejs musi pozostać elastyczny. –

+0

@RobertHarvey To naprawdę nie pomogłoby w tym konkretnym przykładzie. – Servy

+1

@Servy Ten konkretny przykład wydaje się ... uszkodzony. I wymyślone. Dla tego rodzaju rzeczy istnieje wytarta ścieżka. –

Odpowiedz

6

To rozwiązanie działa! Sprawdziłem to.

Problem polega na tym, że powtarzasz wątek interfejsu użytkownika, aby zmienić tekst, ale nigdy nie masz czasu, aby pokazać zaktualizowany tekst. Aby dokonać UI pokazują zmieniony tekst, dodać linię Application.DoEvents tak:

textBox.Text += text; 
Application.DoEvents(); 

PS: : Usuń inny blok pętli If/Else, jest zbędny, a także, jak wskazano przez innych, nie ma żadnego pożytku z tworzenia tych 2 wątków, ponieważ wszystko, co robią, to umieszczanie wiadomości na samym wątku UI.

+0

To rzeczywiście działa, teraz nie mam opóźnień od wątków, nawet uruchamiając wiele wątków;) –

+2

Byłbym ostrożny z 'DoEvents()'. Używaj go oszczędnie; Widziałem programy impasów ludowych używające go bez różnicy. Jeśli poprawnie tworzysz wątki, rzadko powinieneś potrzebować 'DoEvents()'. –

+1

Jeśli mnie pytasz, powinieneś ** nigdy ** używać 'Application.DoEvents()'. W tej sytuacji jest to łatwe do opanowania, ale używanie go zamiast prawidłowego wielowątkowości nie jest nawykiem, do którego powinieneś się dostać. – JosephHirn

2

Ty pokonaniu cel za pomocą nici.

Wątek nie jest informacją UI wątku, aby wykonać kod za pomocą BeginInvoke().

Cała faktyczna praca odbywa się w wątku interfejsu użytkownika.

+0

To wyjaśnia problem, ale nie omawia sposobu jego rozwiązania. – Servy

5

Wciąż wykonuje się zadanie jednowątkowe, w razie potrzeby ponownie uruchamiając go w wątku interfejsu użytkownika.

for (int i = 0; i < 500; i++){ 
    string text = ""+i; 
    textBox.BeginInvoke((MethodInvoker)delegate() 
      { 
       textBox.Text += text; 
      }); 
} 
+0

+1 OP nigdy nie zwalnia wątku UI podczas pętli. – usr

3

Problemem jest to, że zaczynamy nowy wątek, a następnie, że nowy wątek robi nic oprócz dodawania nowego zadania dla jednego wątku UI do procesu, który robi dużo pracy. Aby zachować responsywność formularza, musisz mieć czas, w którym wątek interfejsu użytkownika nic nie robi lub przynajmniej nie spędzać znacznej ilości czasu na wykonywaniu jednego zadania.

Aby zachować responsywność formularza, potrzebujemy dużo małych wywołań BeginInvoke (lub Invoke).

private void writeText(TextBox textBox, string text) 
{ 
    for (int i = 0; i < 500; i++) 
    { 
     Invoke(new MethodInvoker(() => 
     { 
      textBox.Text += text; 
     })); 
    } 
} 

Poprzez losy małego nazywa powołaniem pozwala rzeczy takie jak farby, zdarzeń ruchu myszy/kliknij zdarzeń itp mają być przetwarzane w środku swoich działań. Zauważ też, że usunąłem połączenie InvokeRequired. Mamy know, że ta metoda będzie wywoływana z wątku innego niż UI, więc nie ma takiej potrzeby.

0

Albo przetwarzasz dane, albo próbujesz animować interfejs użytkownika.

Do przetwarzania danych należy wykonać wszystkie operacje podnoszenia ciężarów na wątku w tle i od czasu do czasu aktualizować interfejs użytkownika. W twoim przykładzie TextBox jest szczególnie kłopotliwy pod tym względem, ponieważ dodajesz dane do bazowego modelu danych kilkaset razy, a element UI (TextBox) zajmuje więcej czasu, aby renderować za każdym razem. Musisz być ostrożny, jak często aktualizować interfejs użytkownika, aby przetwarzanie aktualizacji interfejsu użytkownika nie przerastało aktualizacji modeli danych. TextBoxes są takie paskudne.

W poniższym przykładzie, flaga ustawiona podczas imprezy farby zapewnia dodatkowe aktualizacje interfejsu użytkownika nie są w kolejce, aż TextBox zakończeniu malowania ostatnia aktualizacja:

string str = string.Empty; 
public void DoStuff() 
{ 
    System.Threading.ThreadPool.QueueUserWorkItem(WorkerThread); 
} 

void WorkerThread(object unused) 
{ 
    for (int i = 0; i < 1000; i++) 
    { 
     str += "0"; 
     if (updatedUI) 
     { 
      updatedUI = false; 
      BeginInvoke(new Action<string>(UpdateUI), str); 
     } 
    } 
    BeginInvoke(new Action<string>(UpdateUI), str); 
} 

private volatile bool updatedUI = true; 
void textbox1_Paint(object sender, PaintEventArgs e) // event hooked up in Form constructor 
{ 
    updatedUI = true; 
} 

void UpdateUI(string str) 
{ 
    textBox1.Text = str; 
} 

Z drugiej strony, jeśli animacja UI jest Twój cel to prawdopodobnie powinien używać czegoś innego niż TextBox. Po prostu nie jest tak przystosowany do częstych aktualizacji. Mogą istnieć pewne optymalizacje dla renderowania tekstu, który można wykonać dla konkretnego przypadku użycia.

+0

Teraz właśnie próbuję mieć dobry przegląd wielowątkowości. Używam pola tekstowego, ponieważ łatwo zauważyć, że ciągle piszesz do pola tekstowego i że nadal mogę korzystać z formularza. W tej chwili staram się mieć stałe pisanie do pola tekstowego, gdzie można zobaczyć postęp, a także wykonywać inne zadania w tym samym czasie, a nie uruchamiać wątku, widzieć tylko wynik i nie być w stanie korzystać z formularza . –

0

Nigdy nie należy używać napisu w aplikacjach o dużej głośności. UI lub nie. Wielowątkowy lub nie.

Powinieneś używać StringBuilder do gromadzenia napisów. a następnie przypisać

tb.Text = sb.ToString(); 
+0

Mmm .... Ktoś musi poświadczyć twoją odpowiedź ... – kokbira

Powiązane problemy