Problem z blokowaniem wątków C++/C#

0

Mam DLL w C++, która jest podłączana w C#

Problem jest taki:
DLL metoda **A (konstruktor) **inicjuje wątek (std::thread) i działa (wątek obsługujący zdarzenia w DLL)
DLL metoda S zwraca pewne statusy
DLL metoda **B (destruktor) **czeka na zakończenie wątku - join - i kończy pracę DLL

W C# (GUI) inicjuje BackgroundWorker, który odbiera statusy z dll (metoda S), i aktualizuje kontrolki wizualne

Kiedy zrobię coś takiego, że w metodzie BackgroundWorker wywołuję:
Invoke(new Action(() =>
{
SetControls(true);
DLL.B(); //to zawiesi program przy próbie join w metodzie dll-ki DLL.B
}));

Przeniesienie DLL.B poza Invoke rozwiązuje problem
Invoke(new Action(() =>
{
SetControls(true);
}));
DLL.B(); //działa

No ale niestety nie mogę zagwarantować że implementujący DLL będzie zawsze tak robił.

Można to tak zabezpieczyć?

1

Invoke wykonuje kod w głównym wątku. Wykonywanie potencjalnie długotrwałej operacji wewnątrz Invoke nie ma sensu - bo wychodzi tak samo jakby nie było BackgroundWorkera.

0

Przekaż do konstruktora A adres metody (delegata) która zostanie wywołana przez DLL i otrzyma "pewne statusy" jako parametry. Więc nawet BackgroundWorker'a nie trzeba.

0

@_13th_Dragon:
To skrócony opis,
Są inne metody dll do których są przekazane delegaty (metody callback) które również odbierają pewne zdarzenia (komunikaty) z DLL. Wątek w DLL jest potrzebny aby kolejkował komunikaty i przekazywał je do delegatów.
Chodzi bardziej jak rozwiązać potencjalne problemy zakleszczenia przy zwalnianiu DLL.

0

@Azarien:
Samo zwolnienie DLL to milisekundy, wiem że wyniesienie DLL.B poza invoke nie wywali programu, ale czy można tak zabezpieczyć kod w DLL, aby ustrzec się takich implementacji w C#

0

Pozwól by ten wątek z DLL żył własnym życiem, niech sobie kolejkuje,przetwarza,oblicza ...
Cokolwiek robi, nigdy nie czekaj na niego

0

@_13th_Dragon:
:) nie da się
to jest destruktor "kontekstu" DLL, jak zostawiam wątek to mam crash z dostępem do pamięci
Wygląda na to, że trzeba zawrzeć w dokumentacji dobre i złe praktyki podłączania tej DLL.

0
xkpit napisał(a):

@_13th_Dragon:

:) nie da się

Nie ma czegoś takiego.
Zwyczajnie opakuj to w funkcję, która:

  • przyjmuje parametry.
  • na podstawi parametrów tworzy obiekt
  • przy pewnych zdarzeniach (np czasowych) wywołuje callback'a
  • na zakończenie poprawnie wywołuje destruktor.
    Jeżeli generalnie pętla wewnątrz tej funkcji jest nieskończona to niech bada zwrot callback'a - zwrócił false - natychmiast wyskakujemy z pętli.
0

Wątek w dll obsługuje kolejkowanie komunikatów z dll (są również zwracane w dll asynchronicznie). Pętla obsługująca kolejkę jest zabezpieczona (zmienna atomowa przerywa pętlę komunikatów), wyzwalanie obsługi komunikatu z kolejki jest wymuszane zmienną warunkową. Więc wszystko jest - wydaje się- poprawnie. Natomiast join (w skrócie)wywołany z invoke powoduje kompletnie zawieszenie.

0

Po kiego ci ten join?

0

@_13th_Dragon:
Jeśli nie czekam na zakończenie, to mam crash (wątek odwołuje się do zaalokowanych zasobów DLL)

0

Wątek musi być wewnątrz DLL

0
_13th_Dragon napisał(a):

Wątek musi być wewnątrz DLL
Jest, tak jak napisałem.

0

No to czemu wątek zwalnia zasoby przed swoim zakończeniem?

1
xkpit napisał(a):

Wątek w dll obsługuje kolejkowanie komunikatów z dll (są również zwracane w dll asynchronicznie). Pętla obsługująca kolejkę jest zabezpieczona (zmienna atomowa przerywa pętlę komunikatów), wyzwalanie obsługi komunikatu z kolejki jest wymuszane zmienną warunkową. Więc wszystko jest - wydaje się- poprawnie. Natomiast join (w skrócie)wywołany z invoke powoduje kompletnie zawieszenie.

Join może uśpić wątek, z którego został wywołany. Uśpienie wątku głównego zawiesza aplikacje. Czego się spodziewałeś?

Generalnie to ten join jest bez sensu, brzmi jak problem z architekturą tego co tworzysz. Sugerowałbym by wątek sam zażądał swoimi zasobami, a nie na odwrót, tzn zasób zarządzał wątkiem. Owy destruktor B zamiasty zwalniać pamięć, może zakolejkować akcję zwolnienia pamięci w wątku. Ewentualnie jeżeli się nie da, w co wątpię, użyłbym czegoś co ma możliwość atomowego zliczania referencji przed właściwą destrukcją, na przykład std::shared_ptr.

0

@_13th_Dragon:
Może to zobrazuję

0

Wywal w cholerę ten background worker.
Niech callback od razu woła invoke.
Natomiast komunikaty do DLL niech idą synchronicznie.

0

No nie w tym rzecz. Ja sobie mogę w tym programie testowym c# to wywalić. Problem w tym ja się ustrzec takich problemów gdyby ktoś tak właśnie podpiął dll i tak to obsłużył.

1

Nie uchronisz wszystkich przed strzeleniem sobie w stopę.

0

@_13th_Dragon:
Nie do końca rozumiem, mógłbyś to rozwinąć?
Metody zdarzeniowe (callback) w C# odbierają komunikaty z kolejki (DLL - problematyczny wątek.)

0

Dopóki nie podasz pochodzenia danych mogę jedynie zgadywać:

#include <iostream>
#include <thread>
#include <cstdlib>
using namespace std;

struct DllWorkerReportData
{
	int otherData;
};

typedef bool __stdcall DllWorkerReporter(const DllWorkerReportData &data);

struct DllWorkerCreateData
{
	DllWorkerReporter *callback;
	int otherData;
};

class DllWorker
{
	private:
	DllWorkerCreateData params;
	void ThreadTask()
	{
		DllWorkerReportData data;
		while(true)
		{
			// collect data
    		// need lock for read params
			data.otherData=rand()%100;
			if(!params.callback(data)) break;
		}
	}
	void SetCreateData(const DllWorkerCreateData &params)
	{
		// need lock for write params
		this->params=params;
	}
	public:		
	DllWorker(const DllWorkerCreateData &params)
	{
		SetCreateData(params);
	}
	static void ThreadTask(DllWorker &worker)
	{
		worker.ThreadTask();
	}
	static void SetCreateData(DllWorker *worker,const DllWorkerCreateData &params)
	{
		worker->SetCreateData(params);
	}
	static void StartUp(DllWorker *worker)
	{
		try 
		{
		 	thread th(DllWorker.ThreadTask,*worker);
		 	th.join();
		 	delete worker;
		}		
		catch(...) {}
	}
};

__declspec(dllexport) void ChangeCreateData(void *handle,const DllWorkerCreateData &params)
{
	DllWorker::SetCreateData((DllWorker*)handle,params);
}

__declspec(dllexport) void *StartUp(const DllWorkerCreateData &params)
{
	DllWorker *worker=new DllWorker(params);
	thread t1(DllWorker::StartUp(worker));
	return worker;
}

int main()
{
	return 0;
}

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