Samtidig exekvering
En orientering En applikation kan ha flera samtidigt exekverande trådar, threads Ofta används trådar från threadpools. Threadpools effektiviserar hanteringen av trådar Trådar kan återanvändas Automatisk lastfördelning mellan processorer Timers, kan eventuellt utnyttja trådar Asynkrona metoder, Background workers och Tasks är tekniker som underlättar samtidig exekvering Repetitiva arbeten kan effektiviseras med parallell exekvering av loopar
Ett exempel med trådar using System; using System.Threading; class ThreadTest static void Main( ) Thread extratråd = new Thread (WriteY); extratråd.start(); Lägg märke till att det inte går att avgöra i vilken ordning som de båda trådarna exekverar på en dator som enbart har en processor. Även för en dator med flera processorer blir utskriften slumpartad på grund av att de båda trådarna konkurrerar om metoden Consol.WriteLine // Simultaneously, do something on the main thread. for (int i = 0; i < 1000; i++) Console.Write ("x"); static void WriteY( ) for (int i = 0; i < 1000; i++) Console.Write ("y"); yyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyy yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy yyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy yyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyy yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxx
Gemensamma resurser using System; using System.Threading; public class Program private const int maxantal = int.maxvalue; private static long räknare; public static void Main() Thread extratråden = new Thread(Öka); extratråden.start(); Minska(); extratråden.join(); Console.WriteLine("Räknaren: 0", räknare); private static void Minska() for (int i = 0; i < maxantal; i++) räknare--; public static void Öka() for (int i = 0; i < maxantal; i++) räknare++; Resultat blir fel på grund av att räknare är en gemensam resurs för de båda trådarna. Vid contex switch kan beräkningen av ++ eller avbrytas mitt i operationen, vilket leder fel.
Använd lås! using System; using System.Threading; public class Program private static object ettobjekt = new object(); private const int maxantal = int.maxvalue; private static long räknare; public static void Main() Thread extratråden = new Thread(Öka); extratråden.start(); Minska(); extratråden.join(); Console.WriteLine("Räknaren: 0", räknare); I och med du använder lås kan deadlocks uppstå! Vad sker om Öka anropas inifrån det låsta området i Minska och i Minskas låsta område anropas Öka? private static void Minska() for (int i = 0; i < maxantal; i++) lock (ettobjekt) räknare--; public static void Öka() for (int i = 0; i < maxantal; i++) lock (ettobjekt) räknare++;
using System; using System.Threading; Trådar som samarbetar, Set och WaitOne class BasicWaitHandle // AutoResetEvent släpper förbi exakt en tråd åt gången static AutoResetEvent vändkorset = new AutoResetEvent(false); private static int tal = 0; static void Main() new Thread(Waiter).Start(); // Borde göra något vettigt, men sover i det här exemplet... Thread.Sleep(2000); tal = 13; // Släpper förbi exakt en tråd genom vändkorset vändkorset.set(); static void Waiter() // Den exekverande tråden får vänta vid vändkorset Console.WriteLine("Waiting..."); vändkorset.waitone(); // Till dess att vändkorset öppnas för passage Console.WriteLine("Talet är 0.", tal);
ThreadPool using System; using System.Threading; public class Program static void Main() ThreadPool.QueueUserWorkItem(Go); ThreadPool.QueueUserWorkItem(Go, 123); Console.ReadLine(); static void Go(object data) Console.WriteLine( "Hello from the thread pool! " + data); Poolen består av redan färdiga trådar Trådarna återanvänds Antal trådar begränsas till ett rimligt antal Samtidigt exekverande trådar fördelas optimalt mellan processorkärnorna
Exempel på användning av ThreadPool public class Server public static void Main() Console.WriteLine("SERVER"); // Skapar en TCP-lyssnare och lyssnar efter klienter try Console.Write("Serverns ip-adress (till exempel, 127.0.0.1): "); IPAddress ipadress = IPAddress.Parse(Console.ReadLine()); Console.Write("Serverns portnummer (till exempel, 61234): "); int portnr = int.parse(console.readline()); TcpListener tcplyssnaren = new TcpListener(ipAdress, portnr); tcplyssnaren.start(); int antalanslutningar = 0; while (true) Console.WriteLine("Servern väntar på en klient..."); TcpClient nyklient = tcplyssnaren.accepttcpclient(); ThreadPool.QueueUserWorkItem(kommunikation, nyklient); Console.WriteLine("Anslutning 0 är upprättad.", ++antalanslutningar); catch (Exception fel) Console.Error.WriteLine(fel.Message); Den fullständiga koden finns i exemplet KlientServer
Ett exempel med parallell exekvering using System; using System.Diagnostics; using System.Threading.Tasks; public class Program public static void Main() Random slump = new Random(); double[,] xs = GetMatris(300, 400, slump), ys = GetMatris(400, 500, slump), zs = new double[300, 500]; Console.WriteLine("Matriserna är skapade"); var stoppuret = Stopwatch.StartNew(); // Matrismultiplikation, innebär 300 * 400 * 500 = 60 000 000 flyttalsmultiplikationer! // Iterationerna i den yttersta loopen körs via flera samtidigt exekverande trådar. Hur många // samtidiga trådar som kommer att exekvera beror bland annat på antal tillgängliga processorer. Parallel.For(0, zs.getlength(0), i => for (int j = 0; j < zs.getlength(1); j++) zs[i, j] = 0.0; ); for (int k = 0; k < xs.getlength(1); k++) zs[i, j] += xs[i, k] * ys[k, j]; stoppuret.stop(); Console.WriteLine("Uppmätt tid för matrismultiplikationen: " + stoppuret.elapsed);
Timer utan extra trådar public partial class Programfönster : Form private int ticks = 0; private int riktning = -1; private Brush rödpensel = new SolidBrush(Color.Red); private const int DIAMETER = 25; public Programfönster() InitializeComponent(); timerutantråd.interval = 10; Timern är definierad i namnrymden System.Windows.Form Den exekverar utan extra tråd Den är utmärkt för enklare animeringar Den kod som körs vid varje tick får inte vara för omfattande! Den drar sig, det vill säga går inte exakt rätt private void panelrityta_paint(object sender, PaintEventArgs e) int x = (panelrityta.size.width - DIAMETER) / 2; int y = ticks; Graphics grafik = e.graphics; grafik.fillellipse(rödpensel, x, y, DIAMETER, DIAMETER); private void timerutantråd_tick(object sender, EventArgs e) if (ticks <= 0 ticks >= (panelrityta.size.height - DIAMETER)) riktning *= -1; ticks += riktning; panelrityta.refresh();
Timer som använder en extra tråd using System; using System.Timers; Timern är definierad i namnrymden System.Timers Den exekverar via en extra tråd Den är svårare att använda i grafiska gränssnitt på grund av att endast den tråd som är avsedd för det grafiska gränssnittet ska anropa grafiska komponenter, inga andra trådar ska göra det! public class Program public static void Main() System.Timers.Timer timermedtråd = new System.Timers.Timer(); timermedtråd.elapsed += new ElapsedEventHandler(närTimernTickar); timermedtråd.interval = 2000; timermedtråd.enabled = true; Console.WriteLine("Press the Enter key to exit the program."); Console.ReadLine(); // Den här metoden anropas och körs av timerns tråd, det vill säga inte // programmets huvudtråd som exekverar main! private static void närtimerntickar(object source, ElapsedEventArgs e) Console.WriteLine("Hello World!");
Lite om asynkrona metoder Citat från Microsofts webbsida: https://msdn.microsoft.com/en-us/library/mt674882.aspx Asynchrony is essential for activities that are potentially blocking, such as when your application accesses the web. Access to a web resource sometimes is slow or delayed. If such an activity is blocked within a synchronous process, the entire application must wait. In an asynchronous process, the application can continue with other work that doesn't depend on the web resource until the potentially blocking task finishes. Asynchrony proves especially valuable for applications that access the UI thread because all UI-related activity usually shares one thread. If any process is blocked in a synchronous application, all are blocked. Your application stops responding, and you might conclude that it has failed when instead it's just waiting. Citat från: http://msdn.microsoft.com/en-us/library/vstudio/hh156528.aspx The task to which the await operator is applied typically is the return value from a call to a method that implements the Task-Based Asynchronous Pattern
Exempel på en synkron och motsvarande asynkrona metod private void buttonsynkront_click(object sender, EventArgs e) WebClient webbklienten = new WebClient(); string url = textboxurl.text; int indexlastslasch = url.lastindexof('/'); string filnamn = url.substring(indexlastslasch + 1); webbklienten.downloadfile(url, filnamn); MessageBox.Show(filnamn + " har hämtats."); async private void buttonasynkront_click(object sender, EventArgs e) WebClient webbklienten = new WebClient(); string url = textboxurl.text; int indexlastslasch = url.lastindexof('/'); string filnamn = url.substring(indexlastslasch + 1); await webbklienten.downloadfiletaskasync(url, filnamn); MessageBox.Show(filnamn + " har hämtats.");
BackgroundWorker Är en hjälpklass för att hantera trådar Det går att signalera att en worker ska avsluta Det går att använda callback-metoder för att rapportera arbetets fortskridande, att det är avslutat och om arbetet avbröts Kan användas tillsammans med Windows fönster och dess grafiska komponenter Exceptions från en worker går att hantera
Ett exempel med BackGroundWorker, using System; using System.Threading; using System.ComponentModel; del 1 class Program static BackgroundWorker bw; static void Main() bw = new BackgroundWorker(); bw.workerreportsprogress = true; bw.workersupportscancellation = true; bw.dowork += bw_dowork; bw.progresschanged += bw_progresschanged; bw.runworkercompleted += bw_runworkercompleted; bw.runworkerasync("hello to worker"); Console.WriteLine("Press Enter in the next 5 seconds to cancel"); Console.ReadLine(); if (bw.isbusy) bw.cancelasync(); Console.ReadLine();
Ett exempel med BackGroundWorker, del 2 // OBS! bw_dowork får inte anropa några grafiska komponenter!!!! static void bw_dowork(object sender, DoWorkEventArgs e) string meddelande = (string)e.argument; for (int i = 0; i <= 100; i += 20) if (bw.cancellationpending) e.cancel = true; return; bw.reportprogress(i); Thread.Sleep(1000); // Just for the demo... don't go sleeping // for real in pooled threads! //throw new ApplicationException("Testar"); e.result = meddelande.length; // This gets passed to RunWorkerCompleted
Ett exempel med BackGroundWorker, del 3 // bw_runworkercompleted får gärna anropa komponenter som används i det grafiska // användargränsnsittet! static void bw_runworkercompleted(object sender, RunWorkerCompletedEventArgs e) if (e.cancelled) Console.WriteLine("You cancelled!"); else if (e.error!= null) Console.WriteLine("Worker exception: " + e.error.tostring()); else Console.WriteLine("Meddelandets längd: " + e.result); // from DoWork // bw_progresschanged får gärna anropa komponenter som används i det grafiska // användargränsnsittet! static void bw_progresschanged(object sender, ProgressChangedEventArgs e) Console.WriteLine("Reached " + e.progresspercentage + "%");
Ett exempel med en asynkron delegat using System; public class Program private delegate int Arbetare(string text); static void Main() string enrad = "test"; Arbetare arbetaren = Arbete; Console.Write("Skriv en rad: "); enrad = Console.ReadLine(); IAsyncResult pågåendearbete = arbetaren.begininvoke(enrad, null, null); // Här kan vi utföra arbete samtidigt som delegatens metod exekeverar via den extra tråden Console.WriteLine("Vi har precis skickat i väg ett arbete till en annan tråd"); Console.WriteLine("Arbetet exekveras samtidigt som de här båda raderna skrivs ut."); int result = arbetaren.endinvoke(pågåendearbete); Console.WriteLine("\"" + enrad +"\"" + " består av " + result + " tecken."); static int Arbete(string text) return text.length;
Tasks Ett task representerar ett inkapslat arbete som exekverar asynkront Ett task påminner om en delegat men en vanlig delegat exekverar synkront Tasks får sina trådar från ThreadPools Tasks kan meddela när deras arbeten är utförda Tasks kan returnera resultat
using System; using System.Collections.Generic; Exempel med Tasks using System.Threading.Tasks; using AddisonWesley.Michaelis.EssentialCSharp.Shared; public class Program public static void Main() Task<string> task = Task.Run<string>(() => PiCalculator.Calculate(1000)); foreach (char busysymbol in Utility.BusySymbols()) if (task.iscompleted) break; System.Threading.Thread.Sleep(200); Console.Write("\b" + busysymbol); Console.Write('\b'); Console.WriteLine(task.Result); public class Utility public static IEnumerable<char> BusySymbols() string busysymbols = @"-\ /-\ /"; int next = 0; while (true) yield return busysymbols[next]; next = (next + 1) % busysymbols.length; De fullständiga exemplet hittar du i PiCalculator