31. maj 2011 - 12:45Der er
14 kommentarer og 2 løsninger
Backgroundworker og databaseadgang
Hej
Jeg har lavet en forholdsvis simpel Winforms app i c# der henter noget data i en database og viser det på skærmen. For at systemet ikke skal hænge har jeg lagt databasetilgangen ud i en backgroundworker, men nu opstår problemet. De forskellige queries konflikter. F.eks. henter jeg info om menunavne osv i en tabel og noget tekstindhold i en anden tabel. Fordi de har hver deres backgroundworker så når den at gå i gang med at hente tekstindholdet selvom den ikke er færdig med at hente menunavne osv. Er der en bestemt tilgang man normalt bruger? Kan man tjekke om en connection er åben og vente til den bliver lukket. Mit alternative er at bruge RunWorkerCompleted-event'en til at sætte næste databasetilgang igang. Dette virker dog meget omstændigt og uelegant.
Kunne man evt køre det hele med samme backgroundworker?
Eksempel:
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Globalization; using System.ComponentModel;
namespace TestConsole { public interface ITask { void Execute(); bool HasBeenExecuted { get; } object Result { get; } }
class WorkerTask<T> : ITask { private readonly Func<T> _work;
public WorkerTask(Func<T> work) { _work = work; }
public void Execute() { Result = _work.Invoke(); HasBeenExecuted = true; }
public object Result { get; private set; } public bool HasBeenExecuted { get; private set; } }
class Program { static void Main() { BackgroundWorker worker = new BackgroundWorker(); worker.DoWork += DoWork; worker.RunWorkerCompleted += WorkerCompleted;
var tasks = new List<ITask>();
// add some tasks for the backgroundworker to do tasks.Add(new WorkerTask<string>(() => "database operation")); tasks.Add(new WorkerTask<int>(() => 15)); worker.RunWorkerAsync(tasks);
Console.ReadKey(); }
static void DoWork(object sender, DoWorkEventArgs e) { if (!(e.Argument is IList<ITask>)) return;
var tasks = e.Argument as IList<ITask>;
// execute tasks sequentially foreach (var task in tasks) task.Execute();
e.Result = tasks; }
static void WorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if (!(e.Result is IList<ITask>)) return;
var tasks = e.Result as IList<ITask>;
foreach (var task in tasks.Where(t => t.HasBeenExecuted)) { // do stuff with results Console.WriteLine(task.Result); } } } }
@janus_007 - jeg mener det er version 4, men er ikke helt sikkert, ved du hvordan jeg kan tjekke det?
@oneeighty - umiddelbart ville det være en god løsning til at sørge for at der ikke er konfliktende tilgange til databasen, men en del af min idé var også at lade dele af mit gui og indhold loade separat, så app'en ikke hænger men brugeren stadig kan gøre noget.
Er lidt i tvivl om, hvor jeg placerer udførelsen af selve databasequery'en henne. Kan det godt passe at jeg placerer det jeg vil have den til at udføre i denne metode:
Følgende er et eksempel på, hvad jeg f.eks. gerne vil kunne bruge en backgroundworker til:
caseDB.casesDataTable cases;
cases = _casesAdapter.GetData();
cases1.resetBoxes();
cases1.selectedBox = activeBox;
int counter = 0;
foreach (caseDB.casesRow casesRow in cases) { int caseJobs = (int)_jobsAdapter.JobCount(casesRow.caseId); int caseJobsDone = (int)_jobsAdapter.finishCount(casesRow.caseId);
Hvordan vil jeg kunne sende databasetilgangene i dette eksempel til backgroundworker ved brug af dit eksempel? Har iøvrigt brugt de tableAdapters der er indbygget i VSE2008, er det bedre at lave databaseadgangen helt manuelt?
Hvis du bruger .Net 4, så ville jeg helt sikkert anbefale dig at kigge på Task og ContinueWith, ovenstående vil ret nemt kunne styres som du gerne vil :)
Hvis jeg bruger denne metode, er jeg jo vel nærmest nødt til at have en global BackgroundWorker som håndterer alle de tasks der måtte opstå. Men hvordan behandler jeg så resultatet forskelligt alt efter, hvilken task det er den bliver sat i gang med? Og mit problem er jo, at tasks forekommer når brugeren klikker, så jeg jo tilføjer tasks til listen løbende. Kan dette lade sig gøre?
Du kan behandle resultatet ved at kontrollere hvilken type resultatet er
foreach (var task in tasks) { if (task.Result is caseDB.casesDataTable) { var result = task.Result as caseDB.casesDataTable; // gør noget med result } }
Jeg er lidt usikker på hvad det er du ønsker helt konkret umiddelbart ser jeg det ikke nødvendigt med en "global"-backgroundworker ej eller at det ikke kan bruges selvom der bliver klikket på knapper.
Der kan uden tvivl laves bedre og mere generiske løsninger end den jeg har præsenteret dig for, det var som sagt hurtigt lavet og mest ment som inspiration.
Surt... jamen så er du lidt på herrens mark :) En thread er ikke garanteret den eksekveringsorden du efterlyser, så du må selv bygge den som du også er inde på.
Hmm... ok jeg kan se på det hele at jeg lige har lavet en "monster" newbie fejl. Jeg fik ikke lukket database connection og det går det jo lidt svært at udnytte de automatiske concurrency mekanismer.
@oneeighty: Jeg får helt sikkert brug for at kunne lave denne form for task kontrol senere så tak for hjælpen.
@janus_007. Jeg går over til .Net 4.0 snart så regner med at jeg kan kigge på task og continueWith til den tid.
Jeg vil gerne give jer begge point. Kan det lade sig gøre i samme spørgsmål eller skal jeg lave et nyt spm som en af jer kan svare på?
Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.