Асинхронизация: отображение хода выполнения при нажатии кнопки actionButton и отключение других операций для того же пользователя, но разрешить одновременных пользователей - PullRequest
0 голосов
/ 18 октября 2018

Ниже приведен пример кода, который принимает два ввода: 1) входной файл и 2) количество строк ввода.После нажатия кнопки «Анализ» выходные данные команды сервера возвращаются в «Таблицу» на вкладке «Результаты».Это простой пример, когда команда будет выполнена быстро и переключится на панель вкладок «Результаты».

Приведенный ниже код withProgress показывает только индикатор выполнения в течение установленного времени и исчезает, а затем фактический кодказнены.Я хотел бы показать «Сообщение о состоянии» или «Индикатор выполнения» при нажатии «Анализ» и показать, пока команда выполняется.Пока текущий индикатор выполнения запущен, текущий пользователь (другие пользователи могут использовать приложение) не может выполнять какие-либо действия из боковой панели.Потому что в реальном приложении боковая панель имеет больше элементов меню, которые выполняют подобные задачи, и каждая задача имеет кнопку Analyze.Если пользователю разрешено просматривать страницы боковой панели и нажимать Analyze, приложение будет перегружено выполнением нескольких задач.В идеале функциональность индикатора выполнения должна использоваться с несколькими кнопками actionButtons.

Я читаю блоги о async, но не могу поставить правильный код в нужном месте.любая помощь ценится за вознаграждение !!

library(shiny)
library(shinydashboard)
sidebar <- dashboardSidebar(width = 200,
                    sidebarMenu(id = "tabs",
                                menuItem(
                                  "File", tabName = "tab1", icon = icon("fas fa-file")
                                )))
body <- tabItem(tabName = "tab1",
        h2("Input File"),
        fluidRow(
          tabPanel(
            "Upload file",
            value = "upload_file",
            fileInput(
              inputId = "uploadFile",
              label = "Upload Input file",
              multiple = FALSE,
              accept = c(".txt")
            ),
            checkboxInput('header', label = 'Header', TRUE)
          ),
          box(
            title = "Filter X rows",
            width = 7,
            status = "info",
            tabsetPanel(
              id = "input_tab",
              tabPanel(
                "Parameters",
                numericInput(
                  "nrows",
                  label = "Entire number of rows",
                  value = 5,
                  max = 10
                ),
                actionButton("run", "Analyze")
              ),
              tabPanel(
                "Results",
                value = "results",
                navbarPage(NULL,
                           tabPanel(
                             "Table", DT::dataTableOutput("res_table"), 
icon = icon("table")
                           )),
                downloadButton("downList", "Download")
              )
            )
          )
        ))
ui <-
shinyUI(dashboardPage(
dashboardHeader(title = "TestApp", titleWidth = 150),
sidebar,dashboardBody(tabItems(body))
))


server <- function(input, output, session) {
file_rows <- reactiveVal()

observeEvent(input$run, {
withProgress(session, min = 1, max = 15, {
  setProgress(message = 'Analysis in progress',
              detail = 'This may take a while...')
  for (i in 1:15) {
    setProgress(value = i)
    Sys.sleep(0.5)
  }
})
system(paste(
  "cat",
  input$uploadFile$datapath,
  "|",
  paste0("head -", input$nrows) ,
  ">",
  "out.txt"
),
intern = TRUE)
head_rows <- read.delim("out.txt")
file_rows(head_rows)
  })

observeEvent(file_rows(), {
updateTabsetPanel(session, "input_tab", "results")
output$res_table <-
DT::renderDataTable(DT::datatable(
file_rows(),
options = list(
  searching = TRUE,
  pageLength = 10,
  rownames(NULL),
  scrollX = T
  )
  ))
 })

output$downList <- downloadHandler(
filename = function() {
paste0("output", ".txt")
}, content = function(file) {
write.table(file_rows(), file, row.names = FALSE)
}
)
}

shinyApp(ui = ui, server = server)

Ответы [ 3 ]

0 голосов
/ 26 октября 2018

Вот ответ async rstudio , опубликованный Джо Ченгом.Это может кому-то помочь.

0 голосов
/ 26 октября 2018

Вот решение, основанное на (абсолютно под звездной) библиотеке ( ipc ).

Я столкнулся с этой библиотекой из-за вопроса @Dean Attali, гдеДжо Ченг упомянул it.

Краткое руководство guide ipc-пакета дает пример того, что вы просите: AsyncProgress.

Кроме того, он предоставляет пример того, как убить будущее, используя AsyncInterruptor.Однако я пока не смог его протестировать.

Я обошел проблему отмены, используя замечательный пакет @Dean Attali smoothjs , чтобы просто начать новый сеанс и игнорировать старыйFuture (Вы можете улучшить это, используя AsyncInterruptor).

Но, тем не менее, я дал ваш код Future, отбросил ваш system() cmd, потому что в настоящее время я запускаю R на Windows, и нашелспособ отключить (дань @Dean Attali) кнопку анализа по сеансам, дав ей зависимые от сеанса имена:

library(shiny)
library(shinydashboard)
library(ipc)
library(promises)
library(future)
library(shinyjs)
library(datasets)
library(V8)

plan(multiprocess)

jsResetCode <- "shinyjs.reset = function() {history.go(0)}"

header <- dashboardHeader(title = "TestApp", titleWidth = 150)

sidebar <- dashboardSidebar(width = 200,
                            sidebarMenu(id = "tabs",
                                        menuItem(
                                          "File", tabName = "tab1", icon = icon("fas fa-file")
                                        )))

body <- dashboardBody(useShinyjs(),
                      extendShinyjs(text = jsResetCode),
                      fluidRow(column(
                        12, tabItem(
                          tabName = "tab1",
                          h2("Input File"),
                          textOutput("shiny_session"),
                          tabPanel(
                            "Upload file",
                            value = "upload_file",
                            fileInput(
                              inputId = "uploadFile",
                              label = "Upload Input file",
                              multiple = FALSE,
                              accept = c(".txt")
                            ),
                            checkboxInput('header', label = 'Header', TRUE)
                          ),
                          box(
                            title = "Filter X rows",
                            width = 7,
                            status = "info",
                            tabsetPanel(
                              id = "input_tab",
                              tabPanel(
                                "Parameters",
                                numericInput(
                                  "nrows",
                                  label = "Entire number of rows",
                                  value = 5,
                                  max = 10
                                ),
                                column(1, uiOutput("sessionRun")),
                                column(1, uiOutput("sessionCancel"))
                              ),
                              tabPanel(
                                "Results",
                                value = "results",
                                navbarPage(NULL,
                                           tabPanel(
                                             "Table", DT::dataTableOutput("res_table"),
                                             icon = icon("table")
                                           )),
                                downloadButton("downList", "Download")
                              )
                            )
                          )
                        )
                      )))



ui <- shinyUI(dashboardPage(
  header = header,
  sidebar = sidebar,
  body = body,
  title = "TestApp"
))


server <- function(input, output, session) {

  output$shiny_session <-
    renderText(paste("Shiny session:", session$token))

  file_rows <- reactiveVal()

  run_btn_id <- paste0("run_", session$token)
  cancel_btn_id <- paste0("cancel_", session$token)

  output$sessionRun <- renderUI({
    actionButton(run_btn_id, "Analyze")
  })

  output$sessionCancel <- renderUI({
    actionButton(cancel_btn_id, "Cancel")
  })

  paste("Shiny session:", session$token)


  observeEvent(input[[run_btn_id]], {
    file_rows(NULL)

    shinyjs::disable(id = run_btn_id)

    progress <- AsyncProgress$new(message = 'Analysis in progress',
                                  detail = 'This may take a while...')
    row_cnt <- isolate(input$nrows)
    get_header <- isolate(input$header)

    future({
      fileCon <- file("out.txt", "w+", blocking = TRUE)
      linesCnt <- nrow(iris)
      for (i in seq(linesCnt)) {
        Sys.sleep(0.1)
        progress$inc(1 / linesCnt)
        writeLines(as.character(iris$Species)[i],
                   con = fileCon,
                   sep = "\n")
      }
      close(fileCon)
      head_rows <- read.delim("out.txt", nrows = row_cnt, header=get_header)
      progress$close() # Close the progress bar
      return(head_rows)
    }) %...>% file_rows

    return(NULL) # Return something other than the future so we don't block the UI
  })

  observeEvent(input[[cancel_btn_id]],{
    js$reset() # reset shiny session)
  })

  observeEvent(file_rows(), {
    shinyjs::enable(id = run_btn_id)
    updateTabsetPanel(session, "input_tab", "results")
    output$res_table <-
      DT::renderDataTable(DT::datatable(
        req(file_rows()),
        options = list(
          searching = TRUE,
          pageLength = 10,
          rownames(NULL),
          scrollX = T
        )
      ))
  })

  output$downList <- downloadHandler(
    filename = function() {
      paste0("output", ".txt")
    },
    content = function(file) {
      write.table(file_rows(), file, row.names = FALSE)
    }
  )
}

shinyApp(ui = ui, server = server)

Приложение работает:

App running:

0 голосов
/ 25 октября 2018

На этот вопрос ответили на другом форуме

Для дальнейшего использования, если кто-нибудь сталкивается с этим вопросом, вот полный ответ ( Я не придумал этогоответ, это Джо Ченг )


Кажется, это основной фрагмент кода, о котором вы спрашиваете:

  observeEvent(input$run, {
    withProgress(session, min = 1, max = 15, {
      setProgress(message = 'Analysis in progress',
        detail = 'This may take a while...')
      for (i in 1:15) {
        setProgress(value = i)
        Sys.sleep(0.5)
      }
    })
    system(paste(
      "cat",
      input$uploadFile$datapath,
      "|",
      paste0("head -", input$nrows) ,
      ">",
      "out.txt"
    ),
      intern = TRUE)
    head_rows <- read.delim("out.txt")
    file_rows(head_rows)
  })

С фьючерсами / обещаниями вам нужночетко определить, какие операции происходят внутри процесса Shiny, и какие операции будут выполняться в будущем.В этом случае вот шаги, которые мы хотим выполнить, в следующем порядке:

  1. Показать сообщение о ходе выполнения (блестящий процесс)
  2. Чтение реактивов: input $ uploadFile $ datapath, input $nrows (Shiny)
  3. Запишите все, кроме последних nrows, в out.txt (будущий процесс)
  4. Read out.txt (Может быть, скажем, в будущем)
  5. Уволитьprogress (Shiny)
  6. Назначить результат для file_rows (Shiny)

Вот как это выглядит:

observeEvent(input$run, {
  prog <- Progress$new(session)
  prog$set(message = "Analysis in progress",
    detail = "This may take a while...",
    value = NULL)

  path <- input$uploadFile$datapath
  nrows <- input$nrows

  future({
    readLines(path) %>% head(-nrows) %>% writeLines("out.txt")
    read.delim("out.txt")
  }) %...>%
    file_rows() %>%
    finally(~prog$close())
})

Пока конвейер будущего / обещанияпоследнее выражение в наблюдаемом событии (которое в данном случае используется, поскольку file_rows() и finally(...) являются частью конвейера), тогда Shiny будет задерживать обработку любых сообщений от имени пользователя.

Тамэто две вещи, к которым это решение не относится.

  1. Сообщения о прогрессе делают шаг назад;мы не только вынуждены использовать синтаксис Progress$new() вместо чистого withProgress(), но и потеряли возможность сообщать о проценте прогресса.Вы можете попробовать новый пакет ipc для решения этой проблемы.

Это не мешает пользователю щелкать в пользовательском интерфейсе;он не будет ничего делать во время выполнения асинхронной операции, но когда операция будет завершена, эти взаимодействия будут накапливаться в очереди и будут обрабатываться в порядке поступления.Если вы хотите полностью отключить пользовательский интерфейс, чтобы они вообще ничего не могли сделать, в настоящее время в Shiny нет встроенного способа сделать это.Хотя, если подумать, вы можете попытаться заменить использование Progress на showModal(modalDialog(title = "Analysis in progress", "This may take a while...", footer=NULL)); Я думаю, что это по крайней мере остановит щелчки мышью.

...