Tworzenie wątków w pętli a metoda Invoke

0

Wiem, że podobny temat był już wiele razy. Wiem, bo wszystkie przeglądnąłem: i z 4programmers i ze Stacka. Ale w żaden sposób nie mogę otrzymać tego efektu, jaki chcę.

Mam program, który sprawdza ilość wiadomości na skrzynce pocztowej. Do programu wczytuję konta i klikam w przycisk "Sprawdź". Dla każdego konta tworzy mi się osobny wątek - funkcja, która loguje się na dane konto i zlicza wiadomości, wyświetlając przy tym komunikaty w kolumnie status (np. Łączenie, Logowanie, Zliczanie) i liczbę wiadomości w textboxie.

user image

Po wciśnięciu Sprawdź wykonuje się:

private void buttonClick(object sender, EventArgs e)
{
	for (int i = 0; i < mylistview.CheckedItems.Count; i++)
	{
		Thread thread = new Thread(() => messagesCheck(mylistview.CheckedItems[i]));
		thread.Start();
	}
}

a funkcja messagesCheck wygląda tak:

public void messagesCheck(ListViewItem row)
{
	row.SubItems[2].Text = "Łączenie";
	// funkcja do laczenia
	row.SubItems[2].Text = "Logowanie";
	// funkcja do logowania
	row.SubItems[2].Text = "Sprawdzanie";
	//funkcja do zliczania wiadomosci
	textBox3.AppendText("Konto nr " + row.SubItems[0].Text + ": liczba wiadomosci = " + liczba);
}

I zaraz po uruchomieniu, w linijce:

Thread thread = new Thread(() => messagesCheck(mylistview.CheckedItems[i]));

otrzymuję komunikat:

Nieprawidłowa operacja między wątkami: do formantu 'mylistview' uzyskiwany jest dostęp z wątku innego niż wątek, w którym został utworzony.

zależy mi na uruchomieniu fuknkcji messagesCheck w osobnych wątkach dla każdego konta. Bez tego okno do sprawdzania zacina się i wyniki sprawdzania w kolumnie "Status" pojawiają się dopiero po sprawdzeniu wszystkich kont (jednak w textboxie wiadomości pojawiają się na bieżąco, dlaczego???). Spróbowałem też tak:

private void buttonClick(object sender, EventArgs e)
{
	Thread thread = new Thread(() => messagesCheck());
	thread.Start();
}
public void messagesCheck()
{	
	mylistview.Invoke(new Action(delegate()
	{
		for (int i = 0; i < mylistview.CheckedItems.Count; i++)
		{
			row.SubItems[2].Text = "Łączenie";
			// funkcja do laczenia
			row.SubItems[2].Text = "Logowanie";
			// funkcja do logowania
			row.SubItems[2].Text = "Sprawdzanie";
			//funkcja do zliczania wiadomosci
			textBox3.AppendText("Konto nr " + row.SubItems[0].Text + ": liczba wiadomosci = " + liczba);
		}
	 }));
}

Nie było błędu, ale wyniki pokazało dopiero na końcu, tak jak bez wykorzystania wątków.

0

do invoke musisz wstawić TYLKO kod odpowiadający za wyświetlanie czegoś, np. row.SubItems[2].Text = "Łączenie"; a nie cały kod.

0

Invoke jest tylko do UI. Ale ja bym radzil Ci to najpierw dac do jakies threadsafe kolekcji odpowiednio ulozone a pozniej to rozprowadzic. Duzo bezpieczniejsze rozwiazanie (ale troszke, nie znacznie wolniejsze)

0

To w funkcji messageCheck wstawię do Invoke kod odpowiadający za wyświetlanie i tam powinno być OK. Ale ja mam problem z tym forem:

for (int i = 0; i < mylistview.CheckedItems.Count; i++)
{
     Thread thread = new Thread(() => messagesCheck(mylistview.CheckedItems[i]));
     thread.Start();
}
0

Zrób to asynchronicznie to nie będziesz miał takich problemów.
Wątki stosuje się do zadań wymagających dla CPU, a wysyłanie requestów HTTP to nic innego jak czyste IO(czyli najlepiej i najprościej będzie to zrobić z async/await i HttpClient).

Tutaj wątek co najwyżej może się przydać do parsowania już pobranej treści z requesta, ale to w zależności co z tym robisz. Sprawdzenie liczby wiadomości to pewnie odczytanie jednej liczby, więc wątki się tutaj raczej do niczego nie przydadzą.

1

Dlaczego Ci to nie działa. Ano. Do wątku przekazujesz poprzez parametr itema z myListView. Czyli w wątku ląduje element z UI, który został stworzony w innym wątku. Rozumiesz?

Musisz to zrobić asynchronicznie (tak jak napisał some_ONE), ale tak naprawdę nie wiem, czym się różni async od wątku. Druga możliwość to zrobić to "po Bożemu". To oznacza:

  1. Do wątku nie przekazujesz żadnego elementu UI (tak, jak to robisz teraz), tylko jakiś obiekt w stylu Account. Klasa mogłaby wyglądać tak:
 
public class Account
{
  string EMailAddress { get; set; }
  string Login { get; set; }
  string Password { get; set; }
//i inne elementy potrzebne do połączenia i komunikacji.
}

Następnie list view nie wypełniasz bezpośrednio, bezczelnie, tylko po Bożemu:

foreach(Account acc in m_accounts) //m_account to jakaś lista Twoich obiektów Account
{
  AddItemToListView(acc); //tego chyba nie muszę tłumaczyć :) Oczywiście Twój obiekt musi też wylądować w Tagu itema
}

I dopiero teraz możesz zrobić coś takiego:


//tą metodę ustawiasz jako metodę dla wątku - obiekt Account pobierasz z tagu itema z listView
void threadCheck(Account acc, int indexInListView)
{
  //tu się łączysz, sprawdzasz i aktualizujesz status:
  UpdateStatusInListView("Łączenie...", indexInlistView);
}

void UpdateStatusInListView(string status, int indexInListView)
{
  if(m_listView.InvokeNeeded()) //albo InvokeRequired, albo jakoś tak
    m_listView.Invoke(new Action( () => DoUpdateStatus(status, indexInListView));
  else
   DoUpdateStatus(status, indexInListView);
}

void DoUpdateStatus(string status, int indexInListView)
{
  m_listView.Items[indexInListView].SubItems[2].Text = status;
}

Tak będzie po Bożemu i prawidłowo. Jeśli nie rozumiesz dlaczego tak, a nie inaczej, to pytaj.

0

Juhas, dziękuję bardzo za pomoc. Zrobiłem trochę inaczej niż napisałeś, ale nakierowałeś mnie na dobrą drogę. Teraz wszystko działa tak, jak chciałem.

0
Juhas napisał(a):
void UpdateStatusInListView(string status, int indexInListView)
{
  if(m_listView.InvokeNeeded()) //albo InvokeRequired, albo jakoś tak
    m_listView.Invoke(new Action( () => DoUpdateStatus(status, indexInListView));
  else
   DoUpdateStatus(status, indexInListView);
}

void DoUpdateStatus(string status, int indexInListView)
{
  m_listView.Items[indexInListView].SubItems[2].Text = status;
}

Nie trzeba tego rozbijać na dwie metody.

void UpdateStatusInListView(string status, int indexInListView)
{
    if(m_listView.InvokeRequired())
        m_listView.Invoke(new Action<string, int>(UpdateStatusInListView), status, indexInListView);
    else
        m_listView.Items[indexInListView].SubItems[2].Text = status;
}

1 użytkowników online, w tym zalogowanych: 0, gości: 1