使用 Shiny Server 处理用户并发,保持变量独立

Handle user concurrency with Shiny Server, keeping variables separate

您好,

我们在 Shiny Server 免费版上有一个 Shiny 应用程序 运行。当我们对其进行测试时,我们意识到一个用户所做的输入更改会影响另一个用户的会话。我知道每个应用程序只有一个 R 进程 运行,但似乎应该有一种方法可以为各个用户会话保持变量分离。

我在下面创建了一个更简单的示例。请注意,我们的滑块具有一些内置的相互依赖性 - 更改一个滑块会影响其他滑块。完成此操作的方法是跟踪 'old' 和 'new' 值,并按差异的一定比例修改另一个滑块 - sliderB 由对 [= 的任何更改的一小部分进行修改14=],例如。

我怀疑 <<- 和全局变量的使用,但我想不出另一种方法来让它工作。我有一个想法是通过 Javascript 以某种方式在用户端存储变量,但我的想法远远超出了我通常的框框,我不太确定该怎么做。

请注意,我们预计用户总数相对较低,网站启动时可能最多 ~100,通常远低于此。

global.R

a_new <- 10
b_new <- 10
c_new <- 10

a_old <- a_new
b_old <- b_new

server.R

library(shiny)

shinyServer(function(input, output, session) {

  vals <- reactive({
    df <- data.frame(a = input$sliderA,
                     b = input$sliderB,
                     c = input$sliderC)
  })

  observe({
    a_new <- input$sliderA
    print(paste("a_new:", a_new, "a_old:", a_old))
    b_new <- isolate(input$sliderB) + .5 * (a_new - a_old)
    updateSliderInput(session, "sliderB", value = b_new)
    a_old <<- a_new
  })

  observe({
    b_new <- input$sliderB
    print(paste("b_new:", b_new, "b_old:", b_old))
    c_new <- isolate(input$sliderC) + .5 * (b_new - b_old)
    updateSliderInput(session, "sliderC", value = c_new)
    b_old <<- b_new
  })

  output$mytable <- renderTable({
    data.frame(vals())
  })

})

ui.R

library(shiny)

shinyUI(fluidPage(
  sidebarLayout(
    sidebarPanel(
      sliderInput("sliderA",
                  "A:",
                  min = -50,
                  max = 50,
                  value = a_new),
      sliderInput("sliderB",
                  "B:",
                  min = -50,
                  max = 50,
                  value = b_new),
      sliderInput("sliderC",
                  "C:",
                  min = -50,
                  max = 50,
                  value = c_new)
    ),

    mainPanel(
      tableOutput("mytable")
    )
  )
))

代码也可以在这里找到:https://github.com/brianstamper/ShinyApps/tree/master/reactive

下面是一个示例,其中每个会话的所有值和更改都是唯一的。请注意,我将您的 old 变量包装在 reactiveValues() 中。我也改为 observeEvent 因为我认为它更适合你的情况。请注意,更改 sliderA 会导致 sliderB 发生变化,因此 sliderC 也会更新。如果您有任何问题,请告诉我

rm(list = ls())
library(shiny)

a_new <- 10
b_new <- 10
c_new <- 10

ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      sliderInput("sliderA","A:",min = -50,max = 50,value = a_new),
      sliderInput("sliderB","B:",min = -50, max = 50,value = b_new),
      sliderInput("sliderC","C:",min = -50,max = 50,value = c_new)),
    mainPanel(
      tableOutput("mytable")
    )
  )
)

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

  old <- reactiveValues()

  old$a <- 10
  old$b <- 10
  old$c <- 10

  vals <- reactive({
    df <- data.frame(a = input$sliderA,b = input$sliderB,c = input$sliderC)
  })

  observeEvent(input$sliderA,{
    a_new <- input$sliderA
    b_new <- input$sliderB + (.5 * (a_new - old$a))
    updateSliderInput(session, "sliderB", value = b_new)
    old$a <- a_new
  })

  observeEvent(input$sliderB,{
    b_new <- input$sliderB
    c_new <- input$sliderC + (.5 * (b_new - old$b))
    updateSliderInput(session, "sliderC", value = c_new)
    old$b <- b_new
  })

  output$mytable <- renderTable({
    data.frame(vals())
  }) 
}
shinyApp(ui, server) 

事实证明,真正需要的只是更改我声明变量的位置。

根据 Scoping rules for Shiny apps,

The function that you pass to shinyServer() is called once for each session. [...] Everything within this function is instantiated separately for each session.

所以我所要做的就是将 a_oldb_old 的声明放入服务器函数并将它们从 global.R 中删除:

server.R

library(shiny)

shinyServer(function(input, output, session) {
  a_old <- a_new
  b_old <- b_new

  [...]

并发问题确实与使用 reactiveValuesobserveEvent 无关,但是我很高兴了解这些,因为它确实让我不必使用 <<-赋值,即使在将变量声明移入服务器函数后仍然需要赋值。但这里的重要部分是了解哪些变量在所有用户会话之间共享,哪些变量单独实例化,这仅取决于变量的声明位置。