Is it possible to fetch results from solved model asynchronously

Suppose I have a model as follows:

using CSV
using JuMP
using HiGHS
using Random
using DataFrames
Random.seed!(2025)

Z = 3
T = 8760
coef = rand(Float64, (Z,T))

m = Model(HiGHS.Optimizer)
@variable(m, x1[z in 1:Z, t in 1:T]>=0)
@variable(m, x2[z in 1:Z, t in 1:T]>=0)
@constraint(m, c1[z in 1:Z, t in 1:T], m[:x1][z,t] + 2*m[:x2][z,t] <= 2)
@constraint(m, c2[z in 1:Z, t in 1:T], 2*m[:x1][z,t] + m[:x2][z,t] >= 1)
@expression(m, e[z in 1:Z, t in 1:T], coef[z, t]*m[:x1][z,t]+(1-coef[z,t])*m[:x2][z,t])
@objective(m, Min, sum(m[:e][z,t] for z in 1:Z, t in 1:T))

optimize!(m)

@sync begin
    @async begin
        global t1_start = time()
        CSV.write("x1.csv", DataFrame(value.(m[:x1]),:auto))
        global t1_end = time()
        println("Writing x1 done at $t1_end")
    end
    @async begin
        global t2_start = time()
        CSV.write("x2.csv", DataFrame(value.(m[:x2]),:auto))
        global t2_end = time()
        println("Writing x2 done at $t2_end")
    end
end
println("Start time difference is $(t2_start - t1_start)")
println("End time difference is $(t2_end - t1_end)")
println("Average writing time is $((t2_end - t1_end + t2_start - t1_start)/2)")

the time difference is shown as follows:

Start time difference is 0.0315549373626709
End time difference is 0.028921842575073242
Average writing time is 0.03023838996887207

The time difference of two start times is almost same as writing time, does this mean that we could not fetch the results from solved model asynchronously? Is there any way to fulfill asynchronous results fetch?

See the warning in the docstring of @async:

help?> @async
  @async

  Wrap an expression in a Task and add it to the local machine's scheduler queue.

  Values can be interpolated into @async via $, which copies the value directly into the
  constructed underlying closure. This allows you to insert the value of a variable, isolating the
  asynchronous code from changes to the variable's value in the current task.

  │ Warning
  │
  │  It is strongly encouraged to favor Threads.@spawn over @async always even when no
  │  parallelism is required especially in publicly distributed libraries. This is because a
  │  use of @async disables the migration of the parent task across worker threads in the
  │  current implementation of Julia. Thus, seemingly innocent use of @async in a library
  │  function can have a large impact on the performance of very different parts of user
  │  applications.

  │ Julia 1.4
  │
  │  Interpolating values via $ is available as of Julia 1.4.

If you start Julia with julia -t 2 to use two threads, then I get:

using JuMP
import CSV
import DataFrames
import HiGHS
import Random
function main()
    Random.seed!(2025)
    Z, T = 3, 8_760
    coef = rand(Float64, (Z, T))
    model = Model(HiGHS.Optimizer)
    @variable(model, x1[z in 1:Z, t in 1:T] >= 0)
    @variable(model, x2[z in 1:Z, t in 1:T] >= 0)
    @constraint(model, x1 .+ 2 .* x2 .<= 2)
    @constraint(model, 2 .* x1 .+ x2 .>= 1)
    @expression(model, e, coef .* x1 .+ (1 .- coef) .* x2)
    @objective(model, Min, sum(e))
    optimize!(model)
    @sync begin
        Threads.@spawn begin
            global t1_start = time()
            CSV.write("x1.csv", DataFrames.DataFrame(value.(x1), :auto))
            global t1_end = time()
            println("Writing x1 done at $t1_end")
        end
        Threads.@spawn begin
            global t2_start = time()
            CSV.write("x2.csv", DataFrames.DataFrame(value.(x2), :auto))
            global t2_end = time()
            println("Writing x2 done at $t2_end")
        end
    end
    println("Start time difference is $(t2_start - t1_start)")
    println("End time difference is $(t2_end - t1_end)")
    println("Average writing time is $((t2_end - t1_end + t2_start - t1_start)/2)")
end

But note that I’m not 100% sure HiGHS is thread-safe for this use-case. In a larger example you might first want to get the value of all the variables, and then write to a file.

At a higher level though: why do you want to do this? Is this really a bottleneck in your code? If so, is it the value call, or the CSV.write call? You might want to consider something like:

begin
    global t1_start = time()
    v_x1 = value.(x1)
    Threads.@spawn CSV.write("x1.csv", DataFrames.DataFrame(v_x1, :auto))
    global t1_end = time()
    println("Writing x1 done at $t1_end")
end