2017-08-30 64 views
9

Pracuję nad aplikacją, która uzyskuje dostęp do bazy danych SQLite. Problem polega na tym, że DB zostaje zablokowany, gdy pojawia się zapytanie do niego. Zazwyczaj nie stanowi to problemu, ponieważ przepływ aplikacji jest dość liniowy.Zarządzanie wątkami uzyskującymi dostęp do bazy danych za pomocą Java

Jednak mam bardzo długi proces obliczania, który jest uruchamiany przez użytkownika. Ten proces obejmuje wielokrotne wywołania do bazy danych pomiędzy obliczeniami.

Chciałem, aby użytkownik otrzymał wizualną informację zwrotną, więc korzystałem z Javibx progressIndicator i usługi ze struktury Javafx.Concurrency. Problem polega na tym, że użytkownik może swobodnie poruszać się po aplikacji i potencjalnie uruchamiać inne połączenia z bazą danych.

Spowodowało to wyjątek, że plik bazy danych jest zablokowany. Chciałbym sposób, aby zatrzymać ten wątek z uruchomieniem, gdy ten przypadek się dzieje, ale nie udało mi się znaleźć żadnych wyraźnych przykładów w Internecie. Większość z nich jest uproszczona i chciałbym, aby metoda była skalowalna. Próbowałem już użyć metody cancel(), ale to nie gwarantuje, że wątek zostanie anulowany na czas.

Ponieważ nie jestem w stanie sprawdzić wszystkich części kodu w przypadku isCancelled, czasami występuje opóźnienie między momentem anulowania wątku a czasem jego skutecznego zatrzymania.

Więc myślałem o następującym rozwiązaniu, ale chciałbym wiedzieć, czy istnieje lepszy sposób pod względem wydajności i uniknięcia warunków wyścigowych i zawieszenia.

// Start service 
    final CalculatorService calculatorService = new CalculatorService(); 

    // Register service with thread manager 
    threadManager.registerService(CalculatorService); 

    // Show the progress indicator only when the service is running 
    progressIndicator.visibleProperty().bind(calculatorService.runningProperty()); 

calculatorService.setOnSucceeded(new EventHandler<WorkerStateEvent>() { 
     @Override 
     public void handle(WorkerStateEvent workerStateEvent) { 
      System.out.println("SUCCEEDED"); 
      calculatorService.setStopped(true); 
     } 
    }); 

    // If something goes wrong display message 
    calculatorService.setOnFailed(new EventHandler<WorkerStateEvent>() { 
     @Override 
     public void handle(WorkerStateEvent workerStateEvent) { 
      System.out.println("FAILED"); 
      calculatorService.setStopped(true); 
     } 
    }); 

    // Restart the service 
    calculatorService.restart(); 

To jest moje klasy usługi, które mam podklasy zawierać metody, które mogą być wykorzystane, aby ustawić stan usługi (zatrzymany lub nie zatrzymał się)

public class CalculatorService extends Service implements CustomService { 
    private AtomicBoolean stopped; 
    private CalculatorService serviceInstance; 

    public FindBundleService() { 
     stopped = new AtomicBoolean(false); 
     instance = this; 
    } 

    @Override 
    protected Task<Results> createTask() { 
     return new Task<Result>() { 

      @Override 
      protected Result call() throws Exception { 
       try { 
        Result = calculationMethod(this, serviceInstance); 
        return Result; 
       } catch (Exception ex) { 
        // If the thread is interrupted return 
        setStopped(true); 
        return null; 
       } 
      } 
     }; 
    } 

    @Override 
    public boolean isStopped() { 
     return stopped.get(); 
    } 

    @Override 
    public void setStopped(boolean stopped) { 
     this.stopped.set(stopped); 
    } 
} 

Usługa implementuje ten interfejs, który ja zdefiniowano:

public interface CustomService { 

    /** 
    * Method to check if a service has been stopped 
    * 
    * @return 
    */ 
    public boolean isStopped(); 

    /** 
    * Method to set a service as stopped 
    * 
    * @param stopped 
    */ 
    public void setStopped(boolean stopped); 

} 

Wszystkie usługi muszą zarejestrować się w menedżerze wątków, który jest pojedynczą klasą.

public class ThreadManager { 

    private ArrayList<CustomService> services; 

    /** 
    * Constructor 
    */ 
    public ThreadManager() { 
     services = new ArrayList<CustomService>(); 
    } 

    /** 
    * Method to cancel running services 
    */ 
    public boolean cancelServices() { 
     for(CustomService service : services) { 
      if(service.isRunning()) { 
       ((Service) service).cancel(); 
       while(!service.isStopped()) { 
        // Wait for it to stop 
       } 
      } 
     } 
     return true; 
    } 


    /** 
    * Method to register a service 
    */ 
    public void registerService(CustomService service) { 
     services.add(service); 
    } 

    /** 
    * Method to remove a service 
    */ 
    public void removeService(CustomService service) { 
     services.remove(service); 
    } 

} 

W dowolnym miejscu w aplikacji, jeśli chcemy zatrzymać usługę, wywołujemy cancelServices(). To ustawi stan na anulowany Sprawdzam to w moim calculethMethod(), a następnie ustawiam stan, aby zatrzymał się tuż przed powrotem (skutecznie kończąc wątek).

if(task.isCancelled()) { 
     service.setStopped(true); 
     return null; 
} 
+0

Dlaczego wielokrotne wywołania db? Dlaczego nie uzyskać wszystkich potrzebnych informacji w jednym połączeniu? – Sedrick

+0

To dlatego, że robimy pewne obliczenia na podstawie tego, co otrzymaliśmy od DB, a następnie używając wyników, aby uzyskać inne rzeczy z DB. Jest to logika biznesowa, której nie mogę zmienić. – user3552551

+1

Chciałbym wyłączyć wszystkie 'węzły', które wyzwalałyby inne wywołanie db, dopóki nie zakończy się' task'. – Sedrick

Odpowiedz

0

Rozwiązaniem problemu mógłby być Thead.stop(), która została zaniechana wieków temu (można znaleźć więcej na ten temat here).

Aby zaimplementować podobne zachowanie, zaleca się użycie Thread.interrupt(), który jest (w kontekście Task) taki sam jak Task.cancel().

Solutions:

  • Napełnij calculationMethod z isCancelled() kontrole.
  • Spróbuj przerwać operację leżącą za pomocą innego urządzenia Thread.

Drugie rozwiązanie jest chyba to, czego szukasz, ale to zależy od rzeczywistego kodu calculationMethod (co chyba nie można udostępniać).

Generic przykłady zabijania długich operacji na bazie danych (wszystko to wykonywane są z innego wątku):

  • Zabij połączenie z bazą danych (zakładając, że baza danych jest na tyle silny, aby zabić działanie na rozłączyć i następnie odblokuj bazę danych).
  • Poproś o bazę danych, aby zabiła operację (np. kill <SPID>).

EDIT:

ja nie zobaczyć, że został określony do bazy danych SQLite, gdy pisałem moją odpowiedź. Tak aby określić rozwiązania dla SQLite:

  • zabijając połączenia nie pomoże
  • szukać odpowiednika sqlite3_interrupt w interfejsie SQLite java
3

(będę zakładać używasz JDBC dla Twojego kwerendy bazy danych i masz kontrolę nad kodem obsługującym kwerendy)

Chciałbym scentralizować wszystkie dostępy do baz danych w klasie singleton, które utrzymywałyby ostatnie PreparedStatement bieżące zapytanie w pojedynczej e wątek ExecutorService. Następnie można poprosić o wystąpienie pojedynczej instancji takie, jak isQueryRunning(), runQuery(), cancelQuery(), która byłaby synchronized, więc możesz zdecydować o wyświetleniu komunikatu do użytkownika, gdy obliczenia mają zostać anulowane, anulować i rozpocząć nowe.

Coś (dodaj kontrole nieważne i catch (SQLException e) bloków):

public class DB { 

    private Connection cnx; 
    private PreparedStatement lastQuery = null; 
    private ExecutorService exec = Executors.newSingleThreadExecutor(); // So you execute only one query at a time 

    public synchronized boolean isQueryRunning() { 
     return lastQuery != null; 
    } 

    public synchronized Future<ResultSet> runQuery(String query) { 
     // You might want to throw an Exception here if lastQuery is not null (i.e. a query is running) 
     lastQuery = cnx.preparedStatement(query); 
     return exec.submit(new Callable<ResultSet>() { 
      public ResultSet call() { 
       try { 
        return lastQuery.executeQuery(); 
       } finally { // Close the statement after the query has finished and return it to null, synchronizing 
        synchronized (DB.this) { 
         lastQuery.close(); 
         lastQuery = null; 
        } 
       } 
      } 
      // Or wrap the above Future<ResultSet> so that Future.cancel() will actually cancel the query 
    } 

    public synchronized void cancelQuery() { 
     lastQuery.cancel(); // I hope SQLite supports this 
     lastQuery.close(); 
     lastQuery = null; 
    } 

} 
0

Może wywołać instancji wątek T1, metody t1.interrupt(), a następnie w metodzie run wątku (może calculationMethod), dodać instrukcja warunkowa.

public void run() { 
     while (!Thread.currentThread().isInterrupted()) { 
      try { 
       // my code goes here 
      } catch (IOException ex) { 
       log.error(ex,ex) 
      } 
     } 
    } 
0

W trybie WAL (rejestrowania zapisu do przodu) można zrobić wiele zapytań równolegle do sqlite bazie

WAL zapewnia większą współbieżność jako czytelnicy nie blokować pisarzy i pisarza nie blokuje czytelnicy. Czytanie i pisanie może odbywać się równolegle z .

https://sqlite.org/wal.html

Być może te linki są interesujące dla Ciebie:

https://stackoverflow.com/a/6654908/1989579 https://groups.google.com/forum/#!topic/sqlcipher/4pE_XAE14TY https://stackoverflow.com/a/16205732/1989579

Powiązane problemy