Laden...

Asynchron in mehreren Threads Daten Generieren führt hin und wieder zur NullReferenceException?

Erstellt von Sebastian1989101 vor 2 Jahren Letzter Beitrag vor 2 Jahren 311 Views
Sebastian1989101 Themenstarter:in
241 Beiträge seit 2010
vor 2 Jahren
Asynchron in mehreren Threads Daten Generieren führt hin und wieder zur NullReferenceException?

Ich habe jetzt bereits einiges Versucht und so langsam gehen mir die Ideen aus, was dieses Problem auslöst. Vielleicht übersehe ich es auch nur was ich falsch gemacht habe oder der Fehler kommt von fehlendem Wissen, aber ich habe keine Ahnung mehr, wie ich mein Problem weiter eingrenzen kann. Und zwar versuche ich, in mehreren Tasks parallel Aufgaben zu lösen die zu einer Gesamtlösung beitragen sollen. Allerdings erhalte ich manchmal im ersten Durchgang beim Filtern der Ergebnisse eine NullReferenceException, welche sich mir nicht so ganz erschließt. Meine erste Vermutung war ein Timing Problem, aber selbst in ein runter gebrochenem Beispiel tritt dies immer noch von Zeit zu Zeit auf. Also greift dort vllt. der GC ein? Andererseits, warum sollte er - den schließlich sind diese Objekte noch in Verwendung. In der Hoffnung dass ich einfach etwas übersehe oder falsch mache, was hier vllt. jemanden auffällt erstelle ich nun diesen Post mit dem Beispiel - wie gesagt tritt nicht immer auf und wenn den i.d.R. im ersten Durchgang. Das doppelte "ToList()" ist übrigens aus der echten Anwendung weil ich eigtl. mit den Daten noch weiter arbeiten würde - hiermit will ich nur das multiple enumeration vom IEnumerable vermeiden. Im Beispiel verwende ich einfache Strings die aus Random Zahlen bestehen statt die DTO Klassen meiner echten Anwendung - da es damit auch auftritt sollte dies aber keine Rolle spielen.


void Main()
{
	for (var i = 0; i < int.MaxValue; i++)
	{
		SomeWork(i);
	}
}

private List<string> _dataCollection = new List<string>();

public void SomeWork(int run)
{
	var tasks = new List<Task>();
	var rnd = new Random();
	
	_dataCollection.Clear();
	
	for (var i = 0; i < Environment.ProcessorCount; i++)
		tasks.Add(GenerateData(rnd.Next(1, 1000000)));
	
	if (tasks.Any())
	{
		Task.WaitAll(tasks.ToArray());
		foreach (var t in tasks)
			t.Dispose();

		tasks.Clear();
	}
	
	var dataList = _dataCollection.DistinctBy(d => d)?.ToList();
	var resultList = dataList?.Where(d => d.Length > 3).ToList(); // Sometimes NullReferenceException here??

	$"Run {run} finished, results: {resultList.Count}".Dump();
}

public async Task GenerateData(int value)
{
	var rnd = new Random();
	await Task.Delay(3000);	
	_dataCollection.Add(value.ToString());
}

public static class IEnumerableExtension
{
	public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector)
	{
		var seenKeys = new HashSet<TKey>();
		foreach (var element in source)
		{
			if (seenKeys.Add(selector(element)))
				yield return element;
		}
	}
}

Und dies wäre die Exception aus der echten Anwendung zu dem Problem:


[0:] System.NullReferenceException: Object reference not set to an instance of an object.
  at MyApp.Core.ViewModels.SearcherViewModel+<>c.<OnStartSearchCommandExecute>b__97_4 (MyApp.Core.ViewModels.Container.DataContainer b) [0x00000] in F:\Repos\MyApp\MyApp\MyApp\MyApp.Core\ViewModels\SearcherViewModel.cs:372 
  at System.Linq.Enumerable+WhereListIterator`1[TSource].ToList () [0x00017] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/external/corefx/src/System.Linq/src/System/Linq/Where.cs:391 
  at System.Linq.Enumerable.ToList[TSource] (System.Collections.Generic.IEnumerable`1[T] source) [0x0000e] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/external/corefx/src/System.Linq/src/System/Linq/ToCollection.cs:30 
  at MyApp.Core.ViewModels.SearcherViewModel.<OnStartSearchCommandExecute>b__97_0 () [0x008fd] in F:\Repos\MyApp\MyApp\MyApp\MyApp.Core\ViewModels\SearcherViewModel.cs:372 

WAGO Kontakttechnik GmbH & Co. KG / Software Notion
Softwareentwicklung

C# .NET with WPF, ASP, Xamarin and Unity
Personal Blog: Development Blog

16.834 Beiträge seit 2008
vor 2 Jahren

Also greift dort vllt. der GC ein?

Sicher nicht. Du wirst viel eher in eine klassische Race Condition laufen.
Allein wie Du _dataCollection verwendest, ist dies ein klassischer Programmierfehler: gemeinsame Liste, die überhaupt nicht für Multi-Access gedacht ist.

Es ist nicht klar, was Du wirklich vor hast (Du sagst es auch nicht und aus dem Code wird man nicht schlau), aber Aufgaben alá "viele Tasks zur Gesamtlösung" kann man in .NET fast immer mit der TPL umsetzen.
Datenfluss (Task Parallel Library)

Die ist für solche Dinge entwickelt worden.

Sebastian1989101 Themenstarter:in
241 Beiträge seit 2010
vor 2 Jahren

Im Prinzip habe ich X Kombinationen (basierend auf dem User Input, i.d.R. 120 oder 720) für die ich jeweils die besten Optionen aus den Datenbestand errechne. Dies Versuche ich auf so viele Tasks aufzuteilen, wie das verwendete Gerät ans Prozessorkerne hat (auch wenn das einfache Task.Run natürlich nicht garantiert dass dies gleichmäßig Verteilt wird, ist dies für mich gut genug). Da hierbei auch einige Duplikate entstehen Versuche ich anschließend die Duplikate raus zu filtern und anschließend anhand der User Kreterin nochmals die Ergebnisse verfeinern.

Aber ich glaube glatt mit den Hinweis auf Thread Safe hast du, mal wieder, den Tag gerettet. Daran hatte ich absolut nicht mehr gedacht. Ich wäre wohl besser daran wenn mein Field ein ConcurrentBag und keine List ist. Und siehe da, in ersten Tests war dies auch direkt erfolgreich. Eventuell wäre TPL auch noch eine Verbesserung der Lösung - auch wenn ich hiermit noch nie etwas gemacht habe. Ich werde mich da mal durchlesen, vielen Dank. 🙂

WAGO Kontakttechnik GmbH & Co. KG / Software Notion
Softwareentwicklung

C# .NET with WPF, ASP, Xamarin and Unity
Personal Blog: Development Blog

16.834 Beiträge seit 2008
vor 2 Jahren

Hört sich eigentllich für mich nach einer Aufgabe für die Datenquelle an (weil zB. eine Datenbank sowas viel besser (optimieren) kann); aber wenn Du es selbst umsetzen willst, dann wäre TPL the way to go.

Ein ConcurrentBag alleine macht Deine Anwendung meist auch nicht Thread-sicher.
Das macht nur den Zugriff auf die Liste selbst "sicher".

6.911 Beiträge seit 2009
vor 2 Jahren

Hallo Sebastian1989101,

ehrlich gesagt hab ich noch nicht ganz verstandes was erreicht werden soll, aber so wie ich es verstanden habe wäre Parallel Linq / PLinq eine Option dafür.
Duplikate können mit GroupBy "gefiltert" werden, etc.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"