Vorwort
Kürzlich habe ich das Buch „C# Concurrent Programming·Classic Examples“ gelesen. Dies ist kein theoretisches Buch, sondern ein Buch, das hauptsächlich darüber spricht, wie man das aktuelle C#.NET besser nutzen kann Ein Buch dieser APIs wird uns zur Verfügung gestellt. Bei den meisten Büchern handelt es sich um Beispiele, die häufig in der täglichen Entwicklung verwendet werden.
Einige der Ansichten im Buch sind durchaus erfreulich. Beispielsweise sagte der Autor, dass die meisten aktuellen Bücher Inhalte zum gleichzeitigen Multithreading am Ende platzieren, es aber an einer Einführung und Referenz für gleichzeitiges Programmieren mangelt .
Ein anderer Standpunkt ist, dass die überwiegende Mehrheit des inländischen technischen Personals glaubt, dass die Technologie umso leistungsfähiger ist, je niedriger sie ist, während diejenigen, die Anwendungen auf höherer Ebene ausführen, „Code-Farmer“ sind widersetzt sich dieser Ansicht. Tatsächlich ist es auch eine Art Fähigkeit, die vorhandene Bibliothek gut zu nutzen. Obwohl das Verständnis des Grundwissens im täglichen Leben immer noch hilfreich ist, ist es besser, aus abstrakten Konzepten auf höherer Ebene zu lernen.
Asynchrone Grundlagen
Aufgaben anhalten und in den Ruhezustand versetzen
Um Aufgaben asynchron anzuhalten oder in den Ruhezustand zu versetzen, können Sie Task.Delay();
static async Task<T> DelayResult<T>(T result, TimeSpan delay) { await Task.Delay(delay); return result; }
Asynchron verwenden Neustart-Testmechanismus
Eine einfache exponentielle Backoff-Strategie, die Wiederholungszeit erhöht sich allmählich. Diese Strategie wird im Allgemeinen beim Zugriff auf Webdienste verwendet.
static async Task<string> DownloadString(string uri) { using (var client = new HttpClient()) { var nextDealy = TimeSpan.FromSeconds(1); for (int i = 0; i != 3; ++i) { try { return await client.GetStringAsync(uri); } catch { } await Task.Delay(nextDealy); nextDealy = nextDealy + nextDealy; } //最后重试一次,抛出出错信息 return await client.GetStringAsync(uri); } }
Fortschritt melden
Bei asynchronen Vorgängen ist es oft notwendig, den Fortschritt des Vorgangs anzuzeigen, und Sie können IProcess
static async Task MyMethodAsync(IProgress<double> progress) { double precentComplete = 0; bool done = false; while (!done) { await Task.Delay(100); if (progress != null) { progress.Report(precentComplete); } precentComplete++; if (precentComplete == 100) { done = true; } } } public static void Main(string[] args) { Console.WriteLine("starting..."); var progress = new Progress<double>(); progress.ProgressChanged += (sender, e) => { Console.WriteLine(e); }; MyMethodAsync(progress).Wait(); Console.WriteLine("finished"); }
Warten Sie auf eine Gruppe von Aufgaben
Führen Sie mehrere Aufgaben gleichzeitig aus und warten Sie, bis sie alle abgeschlossen sind
Task task1 = Task.Delay(TimeSpan.FromSeconds(1)); Task task2 = Task.Delay(TimeSpan.FromSeconds(2)); Task task3 = Task.Delay(TimeSpan.FromSeconds(1)); Task.WhenAll(task1, task2, task3).Wait();
Warten Sie, bis eine Aufgabe abgeschlossen ist abgeschlossen
Mehrere Aufgaben ausführen, Sie müssen nur auf den Abschluss einer davon reagieren. Es wird hauptsächlich verwendet, um mehrere unabhängige Versuche für einen Vorgang durchzuführen. Solange einer der Versuche abgeschlossen ist, ist die Aufgabe abgeschlossen.
static async Task<int> FirstResponseUrlAsync(string urlA, string urlB) { var httpClient = new HttpClient(); Task<byte[]> downloadTaskA = httpClient.GetByteArrayAsync(urlA); Task<byte[]> downloadTaskB = httpClient.GetByteArrayAsync(urlB); Task<byte[]> completedTask = await Task.WhenAny(downloadTaskA, downloadTaskB); byte[] data = await completedTask; return data.Length; }
Sammlungen
Unveränderliche Stapel und Warteschlangen
Erfordern einen Stapel und eine Warteschlange, die nicht häufig geändert werden und auf die mehrere Threads sicher zugreifen können. Ihre API ist Stack
Wenn in der internen Implementierung ein Objekt überschrieben (neu zugewiesen) wird, gibt die unveränderliche Sammlung eine geänderte Sammlung zurück und die ursprüngliche Sammlungsreferenz ändert sich nicht. Das heißt, wenn eine andere Variable auf dasselbe verweist Objekt, es (andere Variablen) wird sich nicht ändern.
ImmutableStack
var stack = ImmutableStack<int>.Empty; stack = stack.Push(11); var biggerstack = stack.Push(12); foreach (var item in biggerstack) { Console.WriteLine(item); } // output: 12 11 int lastItem; stack = stack.Pop(out lastItem); Console.WriteLine(lastItem); //output: 11
Tatsächlich teilen sich die beiden Stapel den internen Speicher von 11. Diese Implementierung ist sehr effizient und jede Instanz ist threadsicher.
ImmutableQueue
var queue = ImmutableQueue<int>.Empty; queue = queue.Enqueue(11); queue = queue.Enqueue(12); foreach (var item in queue) { Console.WriteLine(item); } // output: 11 12 int nextItem; queue = queue.Dequeue(out nextItem); Console.WriteLine(nextItem); //output: 11
Unveränderliche Listen und Sammlungen
ImmutableList
Zeitkomplexität
Manchmal benötigen Sie eine Datenstruktur, die die Indizierung unterstützt, nicht häufig geändert wird und auf die mehrere Threads sicher zugreifen können.
var list = ImmutableList<int>.Empty; list = list.Insert(0, 11); list = list.Insert(0, 12); foreach (var item in list) { Console.WriteLine(item); } // 12 11
ImmutableList
ImmutableHashSet
Manchmal ist eine solche Datenstruktur erforderlich: Sie muss keinen doppelten Inhalt speichern, wird nicht häufig geändert und kann von mehreren Threads sicher aufgerufen werden. Zeitkomplexität O(log N).
var set = ImmutableHashSet<int>.Empty; set = set.Add(11); set = set.Add(12); foreach (var item in set) { Console.WriteLine(item); } // 11 12 顺序不定
Thread-sicheres Wörterbuch
Eine thread-sichere Schlüssel-Wert-Paar-Sammlung, mehrere Threads können beim Lesen und Schreiben weiterhin die Synchronisation aufrechterhalten.
ConcurrentDictionary
verwendet eine Mischung aus feinkörnigen Sperrtechniken und sperrfreien Techniken und ist einer der praktischsten Sammlungstypen.
var dictionary = new ConcurrentDictionary<int, string>(); dictionary.AddOrUpdate(0, key => "Zero", (key, oldValue) => "Zero");
Wenn mehrere Threads eine gemeinsame Sammlung lesen und schreiben, ist das Dienstprogramm ConcurrentDictionary
Es eignet sich am besten für Situationen, in denen Daten gemeinsam genutzt werden müssen, d. h. mehrere Threads teilen sich eine Sammlung. Wenn einige Threads nur Elemente hinzufügen und andere Threads nur Elemente entfernen, ist es am besten, einen Produzenten/Konsumenten zu verwenden Sammlung (BlockingCollection
Gemeinsame Ressourcen initialisieren
Das Programm verwendet einen Wert an mehreren Stellen und initialisiert ihn beim ersten Zugriff darauf.
static int _simpleVluae; static readonly Lazy<Task<int>> shardAsyncInteger = new Lazy<Task<int>>(async () => { await Task.Delay(2000).ConfigureAwait(false); return _simpleVluae++; }); public static void Main(string[] args) { int shareValue = shardAsyncInteger.Value.Result; Console.WriteLine(shareValue); // 0 shareValue = shardAsyncInteger.Value.Result; Console.WriteLine(shareValue); // 0 shareValue = shardAsyncInteger.Value.Result; Console.WriteLine(shareValue); // 0 }
Das Obige ist der Inhalt der Lesehinweise für C# Concurrent Programming·Classic-Beispiele. Weitere verwandte Inhalte finden Sie auf der chinesischen PHP-Website (www.php.cn)!