Asynchrone Programmierung in .NET 4.5 mit async und await

Asynchrones, nicht blockierendes Programmieren war unter .NET-Entwicklern lange nicht beliebt, denn die bisherigen Verfahren verkomplizierten allesamt den Programmcode. Mit den Schlüsselwörtern async und await in C# 5.0 sowie Visual Basic 11.0 unterscheidet sich nun asynchroner Programmcode nicht mehr wesentlich von der synchronen Vorgehensweise.

Lesezeit: 20 Min.
In Pocket speichern
vorlesen Druckansicht Kommentare lesen 5 Beiträge
Von
  • Holger Schwichtenberg
Inhaltsverzeichnis

In der Vergangenheit war asynchrones, nicht blockierendes Programmieren unter .NET-Entwicklern nicht beliebt, denn Microsoft hatte dafür Verfahren in das .NET Framework eingebaut, die allesamt den Programmcode wesentlich verkomplizierten. Mit den Schlüsselwörtern async und await in C# 5.0 sowie Visual Basic 11.0 unterscheidet sich nun asynchroner Programmcode nicht mehr wesentlich von der stringenten synchronen Vorgehensweise.

Heute wird von Software erwartet, dass sie jederzeit auf Benutzereingaben reagiert, also auch dann, wenn sie gerade mit anderen Aufgaben beschäftigt ist. Diese Forderung ist nicht neu, aber gerade in Zeiten von Wischgesten und stagnierender Prozessorgeschwindigkeiten wichtiger denn je.

Microsoft hatte in .NET 1.0 Möglichkeiten implementiert, Multithreading (mit den Klassen im Namensraum System.Threading) und asynchrone Aufrufe (mit BeginInvoke() und EndInvoke() in der Delegate Klasse) zu nutzen. BeginInvoke() erzeugt dabei einen Thread, und EndInvoke() holt das Ergebnis später ab. Microsoft nannte das Asynchronous Programming Model (APM). In .NET 2.0 hatte das Unternehmen zusätzlich das Event-based Asynchronous Pattern (EAP) eingeführt, bei dem eine Klasse ein Ereignis auslöst, wenn die asynchrone Bearbeitung fertig ist. Ein Beispiel dazu ist die Methode DownloadStringAsync() mit dem zugehörigen Ereignis DownloadStringCompleted() in der Klasse System.Net.WebClient.

Mit .NET 4.0 gab es dann eine allgemeine Abstraktion von Threads in Form von Tasks mit der Task Parallel Library (TPL) sowie Parallel LINQ (PLINQ). TPL bietet eine gute Unterstützung für asynchrone Aufrufe, einschließlich der Festlegung von Abarbeitungsketten (Continuations) und des Abbruchs asynchroner Verarbeitungsprozesse (Cancellation). Dennoch verbleibt auch mit dieser Bibliothek für den Softwareentwickler die Notwendigkeit, den sequenziellen Programmcode erheblich umzustrukturieren, um eine asynchrone Programmierlogik zu realisieren.

C#-Schöpfer Anders Hejlberg sprach schon vor einigen Jahren auf der Professional Developers Conference (PDC) über die Idee von "future variables", bei denen ein Ergebnis eines Funktionsaufrufs im Programmcode zugewiesen wird, obwohl das Ergebnis erst irgendwann in der Zukunft eintrifft. Zur Umsetzung der Idee hat es bis 2012 (.NET 4.5) gedauert. Microsoft nennt das neue Konzept Task-based Asynchronous Pattern (TAP). Es basiert auf den Grundkonzepten der in .NET 4.0 eingeführten TPL, die Microsoft mit zwei neuen Schlüsselwörtern elegant in den Sprachen C# 5.0 und Visual Basic 11.0 verankert hat: async und await in C# beziehungsweise Async und Await in Visual Basic.

Eine Methode kann mit async deklarieren, dass sie plant, im Laufe ihrer Ausführung in einem eigenen Thread weiterzuarbeiten und die Kontrolle an den Aufrufer zurückzugeben. Eine solche asynchrone Methode muss dann ein Task-Objekt (aus der in .NET 4.0 eingeführten TPL) oder nichts (void) zurückliefern. await verwendet man in der asynchronen Methode an der Stelle, an der die asynchrone Ausführung beginnt. Dabei erhält der Aufruf der asynchronen Methode die Kontrolle zurück, während die asynchrone Methode auf die Fertigstellung des neuen Threads wartet und dann ihre eigene Ausführungsfolge nach dem await in diesem Thread fortsetzt.

Voraussetzung, damit wirklich eine asynchrone Ausführung stattfindet, ist, dass die bei await() aufgerufene Methode tatsächlich einen neuen Thread startet und mit await darauf wartet oder eine andere Methode mit await aufruft, die das tut. Weder await noch async starten von sich aus einen Thread. Sie dienen lediglich dazu, Auf- und Rückruf elegant im Programmcode zu verankern. await darf nur in mit async gekennzeichneten Methoden zum Einsatz kommen – das setzt der Sprachcompiler durch. Wenn der Entwickler async ohne await verwendet, warnt die Entwicklungsumgebung, es gibt aber keinen Compiler-Fehler (s. Abb. 1). Es sollte mindestens ein await in einer async-Methode geben.

Eine mit async gekennzeichnete Methode enthält kein await (Abb. 1).

Richtig asynchron formuliert, müsste die Methode aus Abbildung 1 so aussehen:

public async Task<int> DoWorkAsync()
{
Print("DoWorkAsync - Start");
var t = new Task<int>(() => DoWorkInternal());
t.Start();
Print("DoWorkAsync - Mitte");
var r = await t;
Print("DoWorkAsync - Ende");
return r;
}

Ein asynchrone Methode gibt den Rückgabewert nicht direkt zurück, sondern in ein Task-Objekt verpackt. Statt

public int DoWorkAsync(int p)

schreibt der Entwickler also:

public async Task<int> DoWorkAsync(int p)

Die asynchrone Methode kann den Rückgabewert aber ganz normal mit return liefern. Der Compiler kümmert sich um die Verpackung. Das Schlüsselwort await dient zudem dazu, den Rückgabewert einer solchen Methode zu entpacken. Der Entwickler schreibt entweder:

int r = await DoWorkAsync();

oder, wenn er zwischen Aufruf und Warten noch etwas erledigen will:

Task<int> t = DoWorkAsync();
Console.WriteLine("Aufruf gestartet...");
int r = await t;

Beim Einsatz von async und await sind aber weitere Regeln zu beachten. Einsprungpunkte (main()-Routinen) sowie Methoden mit ref- und out-Parametern dürfen nicht mit async gekennzeichnet werden. Ebenso dürfen solche Methoden nicht mit [SecurityCritical] und [SecuritySafeCritical] oder [MethodImpl(MethodImplOptions.Synchronized)] annotiert sein. await ist nicht in Getter- oder Setter-Routinen von Properties, in lock/SyncLock-Blöcken sowie in catch-, finally- und unsafe-Codeblöcken gestattet.

Das Schlüsselwort ist aber nicht auf die Verwendung mit Task-Objekten beschränkt. Ein Entwickler kann auch andere sogenannte "Awaitable"-Klassen entwickeln, die die Methode GetAwaiter() bereitstellen. Sie liefert dann ein "Awaiter"-Objekt, das die Schnittstelle System.Runtime.CompilerServices.INotifyCompletion realisiert. Auf diese Weise kann er auch Klassen, die mit den Pattern APM oder EAP arbeiten, mit einem eigenen "Awaitable"-Wrapper zum Einsatz mit async und await aufbereiten. await ist aber nicht beim Aufruf von Methoden möglich, die void zurückliefern. Das heißt, eine Methodensignatur wie

public async void DoWorkAsync()[/i] 

ist erlaubt, aber lässt sich nicht mit await aufrufen. Daher kommt async void typischerweise nur bei Ereignisbehandlungsroutinen zum Einsatz, die immer void liefern müssen.

Abbildung 2 zeigt ein Ablaufdiagramm für eine einfache asynchrone Befehlsfolge. Die synchron arbeitende MethodeA ruft die mit async gekennzeichnete MethodeB (Pfeil 1). Diese startet einen neuen Task, der die synchrone MethodeC ausführt (Pfeil 2a). Auf das Abarbeiten des Tasks wird mit await gewartet, wodurch der Aufrufer MethodeB die Kontrolle zurückerhält und der Rest der MethodeA ausgeführt wird (Pfeil 2b). Nach dem Ende von C wird der Rest der MethodeB abgearbeitet (Pfeil 3). Diese wird zunächst aus dem aus MethodeA stammenden Thread#1 (gelber Hintergrund) ausgeführt und dann – nach await – als Continuation von Thread#2, in dem die MethodeC erfolgt ist (roter Hintergrund).

Ablaufdiagramm eines asynchronen Aufrufs mit async und await (Abb. 2)

Das folgende Beispiel zeigt einen asynchronen Datenbankzugriff mit Connection, Command und DataReader aus ADO.NET:

public static void run()
{
Console.WriteLine("Run() #1: Aufruf wird initiiert: Thread=" +
System.Threading.Thread.CurrentThread.ManagedThreadId);
ReadDataAsync();
Console.WriteLine("Run() #2: Aufruf ist erfolgt: Thread=" +
System.Threading.Thread.CurrentThread.ManagedThreadId);
}

static private async void ReadDataAsync()
{
Console.WriteLine("Beginn ReadDataAsync: Thread=" +
System.Threading.Thread.CurrentThread.ManagedThreadId);
// Datenbankverbindung asynchron aufbauen
SqlConnection conn = new SqlConnection(@"data source=
.\sqlexpress;initial catalog=WWWings6;integrated security=True;
MultipleActiveResultSets=True;App=ADONETClassic");
await conn.OpenAsync();
Console.WriteLine("Nach Open Async: Thread=" +
System.Threading.Thread.CurrentThread.ManagedThreadId);
// Daten asynchron abrufen
SqlCommand cmd = new SqlCommand("select top(10) * from flug", conn);
var reader = await cmd.ExecuteReaderAsync();
Console.WriteLine("Nach ExecuteReaderAsync: Thread=" +
System.Threading.Thread.CurrentThread.ManagedThreadId);
// Daten ausgeben
while (reader.Read())
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine(reader["Zielort"]);
Console.ForegroundColor = ConsoleColor.Gray;
}

// Verbindung beenden
conn.Close();
Console.WriteLine("Ende ReadDataAsync: Thread=" +
System.Threading.Thread.CurrentThread.ManagedThreadId);
}

In diesen .NET-Klassen gibt es nun zusätzlich zu den bisherigen synchronen auch asynchrone Methoden. Im Beispiel ruft das Hauptprogramm Run() die selbst erstellte, mit async gekennzeichnete Methode ReadDataAsync() auf. Es ist dabei eine Konvention, aber keine Pflicht, dass der Name einer asynchronen Methode auf "async" endet. Die mit so kennzeichnete Methode muss als Rückgabewerte void oder Task<Typ> liefern.

In ihr kommen die von ADO.NET 4.5 bereitgestellten asynchronen Methoden OpenAsync() und ExecuteReaderAsync() zum Einsatz, die jeweils mit await aufgerufen werden. Die Anwendungsart ist hier eine .NET-Konsolenanwendung, in der man die Abfolge leicht visualisieren kann. Die Ausgabe der Thread-Nummern (ManagedThreadId) im langen Quellcode-Beispiel dient lediglich dazu, die asynchrone Ausführung in verschiedenen Threads zu belegen (s. Abb. 3).

Ausgabe der Konsolenanwendung als Beleg für die asynchrone Ausführung in den Threads (Abb. 3)

Führt ein Entwickler Programmcode wie den aus der Konsolen- in einer WPF-Anwendung (Windows Presentation Foundation) aus, wird er sich wundern, dass die ManagedThreadId immer die gleiche ist (s. unten Abb. 4). Dennoch ist die Ausführung asynchron, wie man an der Reihenfolge der Ausgabe sieht, denn "Aufruf ist erfolgt" steht vor "Nach Open Async". Dass Steuerelement-Interaktionen im gleichen Thread (dem UI-Thread) erfolgen, ist eine Leistung des im Hintergrund wirkenden DispatcherSynchronizationContext, der alle Continuations im UI-Thread abarbeitet.

Wenn der Entwickler allerdings auf traditionelle Weise die Eigenschaften von Steuerelementen festlegt, zum Beispiel einen TextBlock mit this.C_Status.Text += ausgabe + "\n"; befüllt, wird er verwundert feststellen, dass die Benutzeroberfläche dennoch blockiert. Nach einem Klick auf die Laden-Schaltfläche in Abbildung 4 kann der Benutzer an der Oberfläche bis zum Ende der Verarbeitung von C_Laden_Click nichts mehr machen, also auch nicht auf Abbrechen klicken. Mit der Blockade wäre die asynchrone Verarbeitung sinnlos.

Die Lösung besteht darin, das Steuerelement nicht direkt zu befüllen, sondern das WPF per Datenbindung erledigen zu lassen. Dazu gibt es in Listing 1 ein sogenanntes Dependency Property, das dann per

<TextBlock Name="C_Status" Height="200" Text="{Binding Status}" />

mit dem TextBlock-Steuerelement C_Status verbunden ist. Ein direkter Steuerelementzugriff ist dennoch im Code enthalten:

C_Daten.ItemsSource = dt.DefaultView; 

Der Zugriff führt allerdings nicht zur Blockade, selbst wenn der Entwickler ihn immer wieder wiederholt.

Die automatische Lenkung aller Continuations in den UI-Thread kann er mit der Übergabe von false an die Methode ConfigureAwait() ausschalten, zum Beispiel

await ReadDataAsync(cts.Token).ConfigureAwait(false)

Dann aber muss er sich selbst darum kümmern, alle Zugriffe auf die Oberfläche wieder auf den UI-Thread zu bringen (z. B. mit dem WPF Dispatcher). Denn WPF quittiert sonst alle Versuche, aus einem anderen als dem UI-Thread auf die Oberfläche zuzugreifen, mit dem Laufzeitfehler: "Der aufrufende Thread kann nicht auf dieses Objekt zugreifen, da sich das Objekt im Besitz eines anderen Threads befindet."

Ausgabe einer mit dem obigen langen Quellcode-Beispiel korrespondieren WPF-Anwendung (Abb. 4)

Listing 1 zeigt die Realisierung des Anwendungscodes für die WPF-Anwendung einschließlich Unterstützung für den Abbruch der Aktion. Dafür erzeugt der Aufrufer mit der Klasse CancellationTokenSource ein CancellationToken-Objekt, das der async-Methode übergeben wird. Der Aufruf kann dann über die Instanz von CancellationTokenSource den Abbruch signalisieren. Der aufgerufene, asynchron ausgeführte Programmcode erfährt über IsCancellationRequested im CancellationToken-Objekt, ob ein Abbruch erwartet wird. Er kann, muss aber darauf nicht reagieren.

Nicht nur die Datenzugriffsklassen des Beispiels, sondern auch andere Klassen in der .NET-Klassenbibliothek haben asynchrone Methoden mit dem .NET Framework 4.5 erhalten; zum Beispiel haben Klassen im Namensraum System.IO nun neue asynchrone Methoden als Pendant zu bestehenden synchronen bekommen. Alle diese Methoden liefern einen Task<Typ> zurück:

  • Klasse System.IO.Stream: ReadAsync(), WriteAsync(), FlushAsync(), CopyToAsync()
  • Klasse System.IO.TextReader (bzw. davon abgeleitete Klassen wie StreamReader): ReadAsync(), ReadBlockAsync(), ReadLineAsync(), ReadToEndAsync()
  • Klasse System.IO.TextWriter (bzw. davon abgeleitete Klassen wie StreamWriter): WriteAsync(), WriteLineAsync(), FlushAsync()

Auch für die Netzwerkprogrammierung hat Microsoft neue asynchrone Methoden für die Task-basierte asynchrone Ausführung eingeführt. Bei den bestehenden Klassen wurde System.Net.WebClient um folgende Methoden erweitert:

  • OpenReadTaskAsync()
  • OpenWriteStringTaskAsync()
  • DownloadDataTaskAsync()
  • DownloadFileTaskAsync()
  • DownloadStringTaskAsync()
  • UploadDataTaskAsync()
  • UploadFileTaskAsync()
  • UploadStringTaskAsync()
  • UploadValuesTaskAsync()

Bei dem Namen fällt auf, dass sie auf "TaskAsync" enden, während in anderen FCL-Klassen der bisherige Name nur um "Async" erweitert wurde. Grund für die Abweichung ist, dass die Klasse WebClient bereits in .NET2.0 asynchrone Methoden (z. B. [i]DownloadStringAsync()) erhalten hat, die einem anderen Entwurfsmuster folgen: Sie lösen ein Ereignis aus (z. B. DownloadStringCompleted()). Dadurch war der Name mit "Async" am Ende schon belegt.

Neben den Erweiterungen für die Klasse System.Net.WebClient gibt es noch die neue Klasse System.Net.Http.HttpClient. Microsoft nennt fünf Argumente, warum sie besser als die alte sei. Allerdings sind diese nicht schlüssig, da man nicht nur von HttpClient, sondern auch von WebClient erben kann. Auch dieser kann viele Anfragen gleichzeitig verwalten, und ebenso unterstützt nun WebClient asynchrone Methoden mit dem neuen Task-Entwurfsmuster. Was dort nicht verraten wird, steht in Diskussionsbeiträgen: HttpClient arbeitet intern viel stärker asynchron als WebClient. Dennoch bleibt die Frage, warum Microsoft schon wieder eine neue API gestalten musste.

Auch die .NET-Klasse System.Net.Sockets.Socket hat jetzt asynchrone Methoden. Beim Erstellen von Proxies für WCF-Dienste (Windows Communication Foundation) bietet Visual Studio nun die Option, statt der bisherigen asynchronen Operationen mit Rückrufereignis auch Methoden gemäß TAP zu erzeugen. Aber Achtung: In beiden Fällen (s. Abb. 5) ist der Methodenname DoWorkAsync(). Bei TAP Pattern gibt es dann jedoch kein DoWorkCompleted()-Ereignis. Der Entwickler muss also aufpassen, dass er da nicht beim Neugenerieren von WCF-Proxies die falsche Option erwischt und dann der Programmcode nicht mehr läuft.

Bei WCF-Proxies hat man die Wahl zwischen dem Task-based Asynchrous Pattern aus NET 4.5 (roter Pfeil) und den alten Async-Event Pattern aus .NET 2.0 (Abb. 5).

Beim Design der Klassen in der mit Windows 8 eingeführten Windows Runtime Library (WinRT) hat Microsoft systematisch das Task-Based Asynchronous Pattern umgesetzt. "Alles, was über 50 Millisekunden dauern könnte, hat nun nur noch asynchrone Methoden", sagte Aleš Holeček auf der BUILD-2011-Konferenz, auf der Microsoft WinRT erstmals vorstellte. Das Unternehmen will die Entwickler erziehen, die aus Vereinfachungsgründen bisher den weit leichteren, aber den Thread blockierenden synchronen Weg gewählt haben. Listing 2 zeigt eine Dateisuche mit WinRT auf Basis von Windows (Desktop) Search, implementiert in .NET 4.5 mit C# 5.0 in einer Konsolenanwendung und unter Verwendung der WinRT-Klassen im Namensraum Windows.Storage.Search und Windows.Storage.

Eine Unterstützung für async und await gibt es noch nicht im objektrelationalen Mapper Entity Framework 5.0, der mit Visual Studio 2012 ausgeliefert wird. Asynchrone Methoden auf Basis des Task-based Asynchronous Pattern wird man erst in Version 6.0 erleben, die derzeit als zweite Alpha-Version vorliegt. Dort gibt es dann asynchrone Methoden sowohl für die Datenabrufe (z. B. ToListAsync(), ToArrayAsync()SingleAsync(), FirstAsync(), SingleOrDefaultAsync() und FirstOrDefaultAsync, CountAsync(), AllAsync(), AnyAsync(), AverageAsync(), MinAsync(), MaxAsync() und SumAsyn()) als auch zum Speichern von neuen, geänderten und gelöschten Objekten (SaveChangesAsync()).

Das Debugging von Anwendungen mit paralleler Verarbeitung ist nicht trivial. Zusätzlich zu dem schon lange vorhandenen "Threads"-Fenster gibt es seit Visual Studio 2010 die "Parallel Tasks" und seit Visual Studio 2012 nun auch "Parallel Stacks" (s. Abb. 6). "Threads" zeigt alle parallel laufenden Threads – auch solche, die Visual Studio zum Debugging braucht. Die "Parallel Tasks"-Liste beschränkt sich auf die aus der Anwendung heraus gestarteten Tasks der TPL. Bei der neuen grafischen Ansicht der "Parallel Stacks" kann man zwischen einer Ansicht der Threads und der der Tasks umschalten. In ihr zeigt ein Kasten jeweils die Aufrufreihenfolge von Methoden. Pfeile repräsentieren Verzweigungen, bei denen die Threads eines Kastens verschiedene Folgemethoden zufällig ausgewählt haben.

In Abbildung 6 zeigt "Parallel Stacks" an, dass sich drei Threads in DoWorkInternal1() befinden. Hingegen sind zwei Threads über DoWorkInternal2() gelaufen, wobei einer der Threads nach DoWorkInternal3() weiterverzweigt ist. Ein weiterer Thread ist im Hauptprogramm (Main). Das korrespondiert mit den Anzeigen in "Threads" und "Parallel Stack". Der gelbe Pfeil in "Parallel Stacks" zeigt an, in welchem Thread sich der Debugger gerade bei der aktuellen Programmcodezeile befindet (hier: Zeile 105 in DoWorkInternal3()). Im "Parallel Stacks" ist der Weg des aktuellen Threads durch blaue Rahmen und Pfeile hervorgehoben.

Debugging-Fenster "Parallel Stacks", "Parallel Tasks" und "Threads" in Visual Studio 2012 (Abb. 6)

Asynchrones Programmieren war in .NET nie so einfach wie mit async und await. Das WPF-Beispiel hat gezeigt, dass Entwickler aber doch einige Punkte im Hinterkopf behalten müssen. Außerdem müssen sie bedenken, dass eine asynchrone Operation grundsätzlich erst einmal etwas länger braucht als eine synchrone – nicht nur für das Erzeugen des eigenen Threads, sondern auch für das Verpacken und Auspacken der Werte in Task-Objekte. Das sind die Kosten der erheblichen Abstraktion, die das Task-based Asynchronous Pattern bietet.

Entscheidend ist aber die subjektiv empfundene Leistung der Software aus Anwendersicht. Und die ist bei einer nichtblockierenden Anwendung höher. Hier sei als weiterführende Lektüre empfohlen: "Async Performance: Understanding the Costs of Async and Await" von Stephen Toub.

Dr. Holger Schwichtenberg
leitet das Expertennetzwerk www.IT-Visions.de, das Beratung, Schulungen und Softwareentwicklung im .NET-Umfeld anbietet. Er hält Vorträge auf Fachkonferenzen und ist Autor zahlreicher Fachbücher.

using System.Data;
using System.Data.SqlClient;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;

namespace WPF45
{
/// <summary>
/// Interaction logic for WPFDataAccessAsync.xaml
/// </summary>
public partial class WPFDataAccessAsync : Window
{
public WPFDataAccessAsync()
{
this.DataContext = this;
InitializeComponent();
}


/// <summary>
/// Dependency Property für Datenbindung an TextBlock C_Status
/// </summary>
public string Status
{
get { return (string)GetValue(StatusProperty); }
set { SetValue(StatusProperty, value); }
}
public static DependencyProperty StatusProperty =
DependencyProperty.Register("Status", typeof(string),
typeof(WPFDataAccessAsync), null);

/// <summary>
/// Verwaltungsobjekt für Abbruch der Aktion
/// </summary>
private CancellationTokenSource cts = null;

private async void C_Laden_Click(object sender, RoutedEventArgs e)
{
cts = new CancellationTokenSource();

Print("C_Laden_Click() #1: Aufruf wird initiiert.");
ReadDataAsync(cts.Token);

Print("C_Laden_Click() #2: Aufruf ist erfolgt.");
}

private void C_Abbrechen_Click(object sender, RoutedEventArgs e)
{
Print("Abbruch des Ladens wird eingeleitet...");
cts.Cancel();
Print("Laden ist abgebrochen!");
}

private void C_Ende_Click(object sender, RoutedEventArgs e)
{
this.Close();
}

/// <summary>
/// Asynchroner Datenzugriff
/// Hinweis: Natürlich gehört Datenzugriffscode normalerweise nicht
/// in die Code-Behind-Datei. Aber hier soll es nur darum gehen, auf
/// übersichtliche Weise das TA-Pattern zu erklären
/// </summary>
private async Task<bool> ReadDataAsync(CancellationToken ct)
{
Status = "";
Print("Beginn ReadDataAsync");
// Datenbankverbindung asynchron aufbauen
var conn =
new SqlConnection(
@"data source=.;initial catalog=WWWings6;integrated security=True;
MultipleActiveResultSets=True;App=ADONETClassic");
await conn.OpenAsync(ct);
Print("Nach OpenAsync");
// Daten asynchron abrufen
var cmd = new SqlCommand("select * from flug", conn);
var reader = await cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection,
ct);
Print("Nach ExecuteReaderAsync");

// Nur zum Test: Ausführungszeit künstlich verlängern, wenn die
// Datenbankabfrage zu schnell ist
for (int i = 0; i < 100; i++)
{
await Task.Delay(50);
if (ct.IsCancellationRequested)
{
Print("Laden wird abgebrochen!");
return false;
}
}

// Verpacken in DataTable zur Datenbindung an GridView
DataTable dt = new DataTable();
dt.Load(reader);
C_Daten.ItemsSource = dt.DefaultView;

// Verbindung beenden
conn.Close();
Print("Ende ReadDataAsync");
return true;
}

private void Print(string s)
{
int tid = System.Threading.Thread.CurrentThread.ManagedThreadId;
string ausgabe = s + " Thread=" + tid;
Status += ausgabe + "\n";
System.Diagnostics.Debug.WriteLine(ausgabe);
}

}
}
static void Main(string[] args)
{
Console.WriteLine("Starte");
DateiSuchDemo("Mountain Bike", @"w:\dokumente");
Console.WriteLine("Warte...");
while (true) { Console.Write("."); System.Threading.Thread.Sleep(100); }
}


/// <summary>
/// Suche ausführen mit Windows Search auf Basis der WinRT-Bibliothek
/// Windows.Storage.Search
/// Auch eine Referenz auf Windows.winmd
/// </summary>
private static async void DateiSuchDemo(string Begriff, string Ordner)
{
// Suche definieren
var docs = Windows.Storage.KnownFolders.DocumentsLibrary;
var queryOptions = new Windows.Storage.Search.QueryOptions();
queryOptions.FolderDepth = Windows.Storage.Search.FolderDepth.Deep;
queryOptions.IndexerOption =
Windows.Storage.Search.IndexerOption.UseIndexerWhenAvailable;
queryOptions.UserSearchFilter = "'" + Begriff + @"' folder:"
+ Ordner; // Advanced Query Syntax (AQS)
var query = docs.CreateFileQueryWithOptions(queryOptions);

// Suche starten
Console.WriteLine("Windows Suche im Ordner " + Ordner + "
nach Dateien mit Begriff: " + Begriff);
Console.WriteLine("Windows Suche beginnt...");

IReadOnlyList<Windows.Storage.StorageFile> ergebnis =
await query.GetFilesAsync();

// Ergebnisse zeigen
Console.WriteLine("\nSuche abgeschlossen. Gefundene Dateien: " +
ergebnis.Count);
Console.WriteLine("-------------------------------------------------");
foreach (Windows.Storage.StorageFile f in ergebnis)
{
Console.WriteLine(f.Name + " (" + f.FileType + "): " + f.Path);
}
} (ane)